1
0
mirror of https://github.com/ppy/osu.git synced 2024-12-16 17:02:55 +08:00

Merge branch 'profile-header-update' of github.com:Jorolf/osu into profile-header-update

This commit is contained in:
jorolf 2019-01-04 22:52:29 +01:00
commit 52994c7ee8
136 changed files with 5139 additions and 2692 deletions

View File

@ -15,12 +15,16 @@ using Microsoft.Win32;
using osu.Desktop.Updater; using osu.Desktop.Updater;
using osu.Framework; using osu.Framework;
using osu.Framework.Platform.Windows; using osu.Framework.Platform.Windows;
using osu.Framework.Screens;
using osu.Game.Screens;
using osu.Game.Screens.Menu;
namespace osu.Desktop namespace osu.Desktop
{ {
internal class OsuGameDesktop : OsuGame internal class OsuGameDesktop : OsuGame
{ {
private readonly bool noVersionOverlay; private readonly bool noVersionOverlay;
private VersionManager versionManager;
public OsuGameDesktop(string[] args = null) public OsuGameDesktop(string[] args = null)
: base(args) : base(args)
@ -46,7 +50,7 @@ namespace osu.Desktop
if (!noVersionOverlay) if (!noVersionOverlay)
{ {
LoadComponentAsync(new VersionManager { Depth = int.MinValue }, v => LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue }, v =>
{ {
Add(v); Add(v);
v.State = Visibility.Visible; v.State = Visibility.Visible;
@ -59,6 +63,23 @@ namespace osu.Desktop
} }
} }
protected override void ScreenChanged(OsuScreen current, Screen newScreen)
{
base.ScreenChanged(current, newScreen);
switch (newScreen)
{
case Intro _:
case MainMenu _:
if (versionManager != null)
versionManager.State = Visibility.Visible;
break;
default:
if (versionManager != null)
versionManager.State = Visibility.Hidden;
break;
}
}
public override void SetHost(GameHost host) public override void SetHost(GameHost host)
{ {
base.SetHost(host); base.SetHost(host);

View File

@ -128,11 +128,12 @@ namespace osu.Desktop.Overlays
protected override void PopIn() protected override void PopIn()
{ {
this.FadeIn(1000); this.FadeIn(1400, Easing.OutQuint);
} }
protected override void PopOut() protected override void PopOut()
{ {
this.FadeOut(500, Easing.OutQuint);
} }
} }
} }

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
namespace osu.Game.Rulesets.Catch.Objects
{
public class CatchHitWindows : HitWindows
{
public override bool IsHitResultAllowed(HitResult result)
{
switch (result)
{
case HitResult.Perfect:
case HitResult.Miss:
return true;
}
return false;
}
}
}

View File

@ -5,6 +5,7 @@ using System;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects; using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -41,5 +42,7 @@ namespace osu.Game.Rulesets.Catch.Scoring
Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness; Health.Value += Math.Max(result.Judgement.HealthIncreaseFor(result) - hpDrainRate, 0) * harshness;
} }
protected override HitWindows CreateHitWindows() => new CatchHitWindows();
} }
} }

View File

@ -5,6 +5,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Judgements; using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -157,5 +158,7 @@ namespace osu.Game.Rulesets.Mania.Scoring
} }
} }
} }
protected override HitWindows CreateHitWindows() => new ManiaHitWindows();
} }
} }

View File

@ -123,8 +123,10 @@ namespace osu.Game.Rulesets.Osu.Difficulty
if (mods.Any(h => h is OsuModFlashlight)) if (mods.Any(h => h is OsuModFlashlight))
{ {
// Apply length bonus again if flashlight is on simply because it becomes a lot harder on longer maps. // Apply object-based bonus for flashlight.
aimValue *= 1.45f * lengthBonus; aimValue *= 1.0f + 0.35f * Math.Min(1.0f, totalHits / 200.0f) +
(totalHits > 200 ? 0.3f * Math.Min(1.0f, (totalHits - 200) / 300.0f) +
(totalHits > 500 ? (totalHits - 500) / 1200.0f : 0.0f) : 0.0f);
} }
// Scale the aim value with accuracy _slightly_ // Scale the aim value with accuracy _slightly_

View File

@ -7,6 +7,7 @@ using osu.Game.Rulesets.Objects.Types;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Game.Rulesets.Objects; using osu.Game.Rulesets.Objects;
using System.Linq; using System.Linq;
using osu.Framework.Caching;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Game.Audio; using osu.Game.Audio;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -26,8 +27,11 @@ namespace osu.Game.Rulesets.Osu.Objects
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity; public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime; public double Duration => EndTime - StartTime;
private Cached<Vector2> endPositionCache;
public override Vector2 EndPosition => endPositionCache.IsValid ? endPositionCache.Value : endPositionCache.Value = Position + this.CurvePositionAt(1);
public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t); public Vector2 StackedPositionAt(double t) => StackedPosition + this.CurvePositionAt(t);
public override Vector2 EndPosition => Position + this.CurvePositionAt(1);
public override int ComboIndex public override int ComboIndex
{ {
@ -56,7 +60,11 @@ namespace osu.Game.Rulesets.Osu.Objects
public SliderPath Path public SliderPath Path
{ {
get => PathBindable.Value; get => PathBindable.Value;
set => PathBindable.Value = value; set
{
PathBindable.Value = value;
endPositionCache.Invalidate();
}
} }
public double Distance => Path.Distance; public double Distance => Path.Distance;
@ -73,6 +81,8 @@ namespace osu.Game.Rulesets.Osu.Objects
if (TailCircle != null) if (TailCircle != null)
TailCircle.Position = EndPosition; TailCircle.Position = EndPosition;
endPositionCache.Invalidate();
} }
} }
@ -92,7 +102,17 @@ namespace osu.Game.Rulesets.Osu.Objects
public List<List<SampleInfo>> NodeSamples { get; set; } = new List<List<SampleInfo>>(); public List<List<SampleInfo>> NodeSamples { get; set; } = new List<List<SampleInfo>>();
public int RepeatCount { get; set; } private int repeatCount;
public int RepeatCount
{
get => repeatCount;
set
{
repeatCount = value;
endPositionCache.Invalidate();
}
}
/// <summary> /// <summary>
/// The length of one span of this <see cref="Slider"/>. /// The length of one span of this <see cref="Slider"/>.
@ -169,7 +189,11 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createTicks() private void createTicks()
{ {
var length = Path.Distance; // A very lenient maximum length of a slider for ticks to be generated.
// This exists for edge cases such as /b/1573664 where the beatmap has been edited by the user, and should never be reached in normal usage.
const double max_length = 100000;
var length = Math.Min(max_length, Path.Distance);
var tickDistance = MathHelper.Clamp(TickDistance, 0, length); var tickDistance = MathHelper.Clamp(TickDistance, 0, length);
if (tickDistance == 0) return; if (tickDistance == 0) return;

View File

@ -5,11 +5,11 @@ using System.Collections.Generic;
using osu.Framework.Extensions; using osu.Framework.Extensions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Judgements; using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects; using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.Osu.Scoring namespace osu.Game.Rulesets.Osu.Scoring
{ {
@ -22,7 +22,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
private float hpDrainRate; private float hpDrainRate;
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>(); private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>();
protected override void ApplyBeatmap(Beatmap<OsuHitObject> beatmap) protected override void ApplyBeatmap(Beatmap<OsuHitObject> beatmap)
@ -35,21 +34,9 @@ namespace osu.Game.Rulesets.Osu.Scoring
protected override void Reset(bool storeResults) protected override void Reset(bool storeResults)
{ {
base.Reset(storeResults); base.Reset(storeResults);
scoreResultCounts.Clear();
comboResultCounts.Clear(); comboResultCounts.Clear();
} }
public override void PopulateScore(ScoreInfo score)
{
base.PopulateScore(score);
score.Statistics[HitResult.Great] = scoreResultCounts.GetOrDefault(HitResult.Great);
score.Statistics[HitResult.Good] = scoreResultCounts.GetOrDefault(HitResult.Good);
score.Statistics[HitResult.Meh] = scoreResultCounts.GetOrDefault(HitResult.Meh);
score.Statistics[HitResult.Miss] = scoreResultCounts.GetOrDefault(HitResult.Miss);
}
private const double harshness = 0.01; private const double harshness = 0.01;
protected override void ApplyResult(JudgementResult result) protected override void ApplyResult(JudgementResult result)
@ -59,10 +46,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
var osuResult = (OsuJudgementResult)result; var osuResult = (OsuJudgementResult)result;
if (result.Type != HitResult.None) if (result.Type != HitResult.None)
{
scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1;
comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1; comboResultCounts[osuResult.ComboType] = comboResultCounts.GetOrDefault(osuResult.ComboType) + 1;
}
switch (result.Type) switch (result.Type)
{ {
@ -89,5 +73,7 @@ namespace osu.Game.Rulesets.Osu.Scoring
} }
protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement); protected override JudgementResult CreateResult(Judgement judgement) => new OsuJudgementResult(judgement);
protected override HitWindows CreateHitWindows() => new OsuHitWindows();
} }
} }

View File

@ -3,6 +3,7 @@
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Objects; using osu.Game.Rulesets.Taiko.Objects;
using osu.Game.Rulesets.UI; using osu.Game.Rulesets.UI;
@ -65,5 +66,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
Health.Value = 0; Health.Value = 0;
} }
protected override HitWindows CreateHitWindows() => new TaikoHitWindows();
} }
} }

View File

@ -29,7 +29,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{ {
try try
{ {
loadOszIntoOsu(loadOsu(host)); LoadOszIntoOsu(loadOsu(host));
} }
finally finally
{ {
@ -48,7 +48,7 @@ namespace osu.Game.Tests.Beatmaps.IO
{ {
var osu = loadOsu(host); var osu = loadOsu(host);
var imported = loadOszIntoOsu(osu); var imported = LoadOszIntoOsu(osu);
deleteBeatmapSet(imported, osu); deleteBeatmapSet(imported, osu);
} }
@ -69,8 +69,8 @@ namespace osu.Game.Tests.Beatmaps.IO
{ {
var osu = loadOsu(host); var osu = loadOsu(host);
var imported = loadOszIntoOsu(osu); var imported = LoadOszIntoOsu(osu);
var importedSecondTime = loadOszIntoOsu(osu); var importedSecondTime = LoadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.ID == importedSecondTime.ID);
@ -105,7 +105,7 @@ namespace osu.Game.Tests.Beatmaps.IO
manager.ItemAdded += (_, __, ___) => fireCount++; manager.ItemAdded += (_, __, ___) => fireCount++;
manager.ItemRemoved += _ => fireCount++; manager.ItemRemoved += _ => fireCount++;
var imported = loadOszIntoOsu(osu); var imported = LoadOszIntoOsu(osu);
Assert.AreEqual(0, fireCount -= 1); Assert.AreEqual(0, fireCount -= 1);
@ -160,12 +160,12 @@ namespace osu.Game.Tests.Beatmaps.IO
var osu = loadOsu(host); var osu = loadOsu(host);
var manager = osu.Dependencies.Get<BeatmapManager>(); var manager = osu.Dependencies.Get<BeatmapManager>();
var imported = loadOszIntoOsu(osu); var imported = LoadOszIntoOsu(osu);
imported.Hash += "-changed"; imported.Hash += "-changed";
manager.Update(imported); manager.Update(imported);
var importedSecondTime = loadOszIntoOsu(osu); var importedSecondTime = LoadOszIntoOsu(osu);
Assert.IsTrue(imported.ID != importedSecondTime.ID); Assert.IsTrue(imported.ID != importedSecondTime.ID);
Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID); Assert.IsTrue(imported.Beatmaps.First().ID < importedSecondTime.Beatmaps.First().ID);
@ -191,11 +191,11 @@ namespace osu.Game.Tests.Beatmaps.IO
{ {
var osu = loadOsu(host); var osu = loadOsu(host);
var imported = loadOszIntoOsu(osu); var imported = LoadOszIntoOsu(osu);
deleteBeatmapSet(imported, osu); deleteBeatmapSet(imported, osu);
var importedSecondTime = loadOszIntoOsu(osu); var importedSecondTime = LoadOszIntoOsu(osu);
// check the newly "imported" beatmap is actually just the restored previous import. since it matches hash. // check the newly "imported" beatmap is actually just the restored previous import. since it matches hash.
Assert.IsTrue(imported.ID == importedSecondTime.ID); Assert.IsTrue(imported.ID == importedSecondTime.ID);
@ -262,7 +262,7 @@ namespace osu.Game.Tests.Beatmaps.IO
} }
} }
private string createTemporaryBeatmap() private static string createTemporaryBeatmap()
{ {
var temp = Path.GetTempFileName() + ".osz"; var temp = Path.GetTempFileName() + ".osz";
File.Copy(TEST_OSZ_PATH, temp, true); File.Copy(TEST_OSZ_PATH, temp, true);
@ -270,7 +270,7 @@ namespace osu.Game.Tests.Beatmaps.IO
return temp; return temp;
} }
private BeatmapSetInfo loadOszIntoOsu(OsuGameBase osu, string path = null) public static BeatmapSetInfo LoadOszIntoOsu(OsuGameBase osu, string path = null)
{ {
var temp = path ?? createTemporaryBeatmap(); var temp = path ?? createTemporaryBeatmap();
@ -305,7 +305,7 @@ namespace osu.Game.Tests.Beatmaps.IO
return osu; return osu;
} }
private void ensureLoaded(OsuGameBase osu, int timeout = 60000) private static void ensureLoaded(OsuGameBase osu, int timeout = 60000)
{ {
IEnumerable<BeatmapSetInfo> resultSets = null; IEnumerable<BeatmapSetInfo> resultSets = null;
var store = osu.Dependencies.Get<BeatmapManager>(); var store = osu.Dependencies.Get<BeatmapManager>();
@ -343,7 +343,7 @@ namespace osu.Game.Tests.Beatmaps.IO
Assert.IsTrue(beatmap?.HitObjects.Any() == true); Assert.IsTrue(beatmap?.HitObjects.Any() == true);
} }
private void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000) private static void waitForOrAssert(Func<bool> result, string failureMessage, int timeout = 60000)
{ {
Task task = Task.Run(() => Task task = Task.Run(() =>
{ {

View File

@ -1,135 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseDrawableRoom : OsuTestCase
{
private RulesetStore rulesets;
protected override void LoadComplete()
{
base.LoadComplete();
DrawableRoom first;
Add(new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Y,
Width = 580f,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
first = new DrawableRoom(new Room
{
Name = { Value = @"Great Room Right Here" },
Host = { Value = new User { Username = @"Naeferith", Id = 9492835, Country = new Country { FlagName = @"FR" } } },
Status = { Value = new RoomStatusOpen() },
Type = { Value = new GameTypeTeamVersus() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 4.65,
Ruleset = rulesets.GetRuleset(3),
Metadata = new BeatmapMetadata
{
Title = @"Critical Crystal",
Artist = @"Seiryu",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh//beatmaps/376340/covers/cover.jpg?1456478455",
},
},
},
},
},
Participants =
{
Value = new[]
{
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1355 } } },
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 8756 } } },
},
},
}),
new DrawableRoom(new Room
{
Name = { Value = @"Relax It's The Weekend" },
Host = { Value = new User { Username = @"peppy", Id = 2, Country = new Country { FlagName = @"AU" } } },
Status = { Value = new RoomStatusPlaying() },
Type = { Value = new GameTypeTagTeam() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 1.96,
Ruleset = rulesets.GetRuleset(0),
Metadata = new BeatmapMetadata
{
Title = @"Serendipity",
Artist = @"ZAQ",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh//beatmaps/526839/covers/cover.jpg?1493815706",
},
},
},
},
},
Participants =
{
Value = new[]
{
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 578975 } } },
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 24554 } } },
},
},
}),
}
});
AddStep(@"select", () => first.State = SelectionState.Selected);
AddStep(@"change title", () => first.Room.Name.Value = @"I Changed Name");
AddStep(@"change host", () => first.Room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
AddStep(@"change status", () => first.Room.Status.Value = new RoomStatusPlaying());
AddStep(@"change type", () => first.Room.Type.Value = new GameTypeVersus());
AddStep(@"change beatmap", () => first.Room.Beatmap.Value = null);
AddStep(@"change participants", () => first.Room.Participants.Value = new[]
{
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 1254 } } },
new User { Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 123189 } } },
});
AddStep(@"deselect", () => first.State = SelectionState.NotSelected);
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}
}
}

View File

@ -1,216 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Multi.Screens.Lounge;
using osu.Game.Users;
using osuTK.Input;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseLounge : ManualInputManagerTestCase
{
private TestLounge lounge;
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
lounge = new TestLounge();
Room[] rooms =
{
new Room
{
Name = { Value = @"Just Another Room" },
Host = { Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } } },
Status = { Value = new RoomStatusPlaying() },
Availability = { Value = RoomAvailability.Public },
Type = { Value = new GameTypeTagTeam() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 5.65,
Ruleset = rulesets.GetRuleset(0),
Metadata = new BeatmapMetadata
{
Title = @"Sidetracked Day (Short Ver.)",
Artist = @"VINXIS",
AuthorString = @"Hobbes2",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/767600/covers/cover.jpg?1526243446",
},
},
},
}
},
MaxParticipants = { Value = 10 },
Participants =
{
Value = new[]
{
new User { Username = @"flyte", Id = 3103765, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 142 } } },
new User { Username = @"Cookiezi", Id = 124493, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 546 } } },
new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 287 } } },
new User { Username = @"Rafis", Id = 2558286, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 468 } } },
new User { Username = @"hvick225", Id = 50265, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 325 } } },
new User { Username = @"peppy", Id = 2, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 625 } } },
}
}
},
new Room
{
Name = { Value = @"Not Just Any Room" },
Host = { Value = new User { Username = @"Monstrata", Id = 2706438, Country = new Country { FlagName = @"CA" } } },
Status = { Value = new RoomStatusOpen() },
Availability = { Value = RoomAvailability.FriendsOnly },
Type = { Value = new GameTypeTeamVersus() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 2.73,
Ruleset = rulesets.GetRuleset(0),
Metadata = new BeatmapMetadata
{
Title = @"lit(var)",
Artist = @"kensuke ushio",
AuthorString = @"Monstrata",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/623972/covers/cover.jpg?1521167183",
},
},
},
}
},
Participants =
{
Value = new[]
{
new User { Username = @"Jeby", Id = 3136279, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 3497 } } },
new User { Username = @"DualAkira", Id = 5220933, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 643 } } },
new User { Username = @"Datenshi Yohane", Id = 7171857, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 10555 } } },
}
}
},
new Room
{
Name = { Value = @"room THE FINAL" },
Host = { Value = new User { Username = @"Delis", Id = 1603923, Country = new Country { FlagName = @"JP" } } },
Status = { Value = new RoomStatusPlaying() },
Availability = { Value = RoomAvailability.Public },
Type = { Value = new GameTypeTagTeam() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 4.48,
Ruleset = rulesets.GetRuleset(3),
Metadata = new BeatmapMetadata
{
Title = @"ONIGIRI FREEWAY",
Artist = @"OISHII",
AuthorString = @"Mentholzzz",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/663098/covers/cover.jpg?1521898837",
},
},
},
}
},
MaxParticipants = { Value = 30 },
Participants =
{
Value = new[]
{
new User { Username = @"KizuA", Id = 6510442, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 5372 } } },
new User { Username = @"Colored", Id = 827563, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 810 } } },
new User { Username = @"Beryl", Id = 3817591, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 10096 } } },
}
}
},
};
AddStep(@"show", () => Add(lounge));
AddStep(@"set rooms", () => lounge.Rooms = rooms);
selectAssert(0);
AddStep(@"clear rooms", () => lounge.Rooms = new Room[] {});
AddAssert(@"no room selected", () => lounge.SelectedRoom == null);
AddStep(@"set rooms", () => lounge.Rooms = rooms);
selectAssert(1);
AddStep(@"open room 1", () => clickRoom(1));
AddUntilStep(() => lounge.ChildScreen?.IsCurrentScreen == true, "wait until room current");
AddStep(@"make lounge current", lounge.MakeCurrent);
filterAssert(@"THE FINAL", LoungeTab.Public, 1);
filterAssert(string.Empty, LoungeTab.Public, 2);
filterAssert(string.Empty, LoungeTab.Private, 1);
filterAssert(string.Empty, LoungeTab.Public, 2);
filterAssert(@"no matches", LoungeTab.Public, 0);
AddStep(@"clear rooms", () => lounge.Rooms = new Room[] {});
AddStep(@"set rooms", () => lounge.Rooms = rooms);
AddAssert(@"no matches after clear", () => !lounge.ChildRooms.Any());
filterAssert(string.Empty, LoungeTab.Public, 2);
AddStep(@"exit", lounge.Exit);
}
private void clickRoom(int n)
{
InputManager.MoveMouseTo(lounge.ChildRooms.ElementAt(n));
InputManager.Click(MouseButton.Left);
}
private void selectAssert(int n)
{
AddStep($@"select room {n}", () => clickRoom(n));
AddAssert($@"room {n} selected", () => lounge.SelectedRoom == lounge.ChildRooms.ElementAt(n).Room);
}
private void filterAssert(string filter, LoungeTab tab, int endCount)
{
AddStep($@"filter '{filter}', {tab}", () => lounge.SetFilter(filter, tab));
AddAssert(@"filtered correctly", () => lounge.ChildRooms.Count() == endCount);
}
private class TestLounge : Lounge
{
protected override BackgroundScreen CreateBackground() => new BackgroundScreenDefault();
public IEnumerable<DrawableRoom> ChildRooms => RoomsContainer.Children.Where(r => r.MatchingFilter);
public Room SelectedRoom => Inspector.Room;
public void SetFilter(string filter, LoungeTab tab)
{
Filter.Search.Current.Value = filter;
Filter.Tabs.Current.Value = tab;
}
}
}
}

View File

@ -0,0 +1,101 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi;
using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Users;
using osuTK.Graphics;
namespace osu.Game.Tests.Visual
{
public class TestCaseLoungeRoomsContainer : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(RoomsContainer),
typeof(DrawableRoom)
};
[Cached(Type = typeof(IRoomManager))]
private TestRoomManager roomManager = new TestRoomManager();
public TestCaseLoungeRoomsContainer()
{
RoomsContainer container;
Child = container = new RoomsContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Width = 0.5f,
JoinRequested = joinRequested
};
AddStep("clear rooms", () => roomManager.Rooms.Clear());
AddStep("add rooms", () =>
{
for (int i = 0; i < 3; i++)
{
roomManager.Rooms.Add(new Room
{
RoomID = { Value = i },
Name = { Value = $"Room {i}" },
Host = { Value = new User { Username = "Host" } },
EndDate = { Value = DateTimeOffset.Now + TimeSpan.FromSeconds(10) }
});
}
});
AddAssert("has 2 rooms", () => container.Rooms.Count == 3);
AddStep("remove first room", () => roomManager.Rooms.Remove(roomManager.Rooms.FirstOrDefault()));
AddAssert("has 2 rooms", () => container.Rooms.Count == 2);
AddAssert("first room removed", () => container.Rooms.All(r => r.Room.RoomID.Value != 0));
AddStep("select first room", () => container.Rooms.First().Action?.Invoke());
AddAssert("first room selected", () => container.SelectedRoom.Value == roomManager.Rooms.First());
AddStep("join first room", () => container.Rooms.First().Action?.Invoke());
AddAssert("first room joined", () => roomManager.Rooms.First().Status.Value is JoinedRoomStatus);
}
private void joinRequested(Room room) => room.Status.Value = new JoinedRoomStatus();
private class TestRoomManager : IRoomManager
{
public event Action RoomsUpdated;
public readonly BindableCollection<Room> Rooms = new BindableCollection<Room>();
IBindableCollection<Room> IRoomManager.Rooms => Rooms;
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => Rooms.Add(room);
public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
{
}
public void PartRoom()
{
}
public void Filter(FilterCriteria criteria)
{
}
}
private class JoinedRoomStatus : RoomStatus
{
public override string Message => "Joined";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Yellow;
}
}
}

View File

@ -1,142 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Screens.Match;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseMatch : OsuTestCase
{
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
Room room = new Room
{
Name = { Value = @"One Awesome Room" },
Status = { Value = new RoomStatusOpen() },
Availability = { Value = RoomAvailability.Public },
Type = { Value = new GameTypeTeamVersus() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 5.02,
Ruleset = rulesets.GetRuleset(1),
Metadata = new BeatmapMetadata
{
Title = @"Paradigm Shift",
Artist = @"Morimori Atsushi",
AuthorString = @"eiri-",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/765055/covers/cover.jpg?1526955337",
},
},
},
},
},
MaxParticipants = { Value = 5 },
Participants =
{
Value = new[]
{
new User
{
Username = @"eiri-",
Id = 3388410,
Country = new Country { FlagName = @"US" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/3388410/00a8486a247831e1cc4375db519f611ac970bda8bc0057d78b0f540ea38c3e58.jpeg",
IsSupporter = true,
},
new User
{
Username = @"Nepuri",
Id = 6637817,
Country = new Country { FlagName = @"DE" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/6637817/9085fc60248b6b5327a72c1dcdecf2dbedba810ae0ab6bcf7224e46b1339632a.jpeg",
IsSupporter = true,
},
new User
{
Username = @"goheegy",
Id = 8057655,
Country = new Country { FlagName = @"GB" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/8057655/21cec27c25a11dc197a4ec6a74253dbabb495949b0e0697113352f12007018c5.jpeg",
},
new User
{
Username = @"Alumetri",
Id = 5371497,
Country = new Country { FlagName = @"RU" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/5371497/e023b8c7fbe3613e64bd4856703517ea50fbed8a5805dc9acda9efe9897c67e2.jpeg",
},
}
},
};
Match match = new Match(room);
AddStep(@"show", () => Add(match));
AddStep(@"null beatmap", () => room.Beatmap.Value = null);
AddStep(@"change name", () => room.Name.Value = @"Two Awesome Rooms");
AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
AddStep(@"change availability", () => room.Availability.Value = RoomAvailability.FriendsOnly);
AddStep(@"change type", () => room.Type.Value = new GameTypeTag());
AddStep(@"change beatmap", () => room.Beatmap.Value = new BeatmapInfo
{
StarDifficulty = 4.33,
Ruleset = rulesets.GetRuleset(2),
Metadata = new BeatmapMetadata
{
Title = @"Yasashisa no Riyuu",
Artist = @"ChouCho",
AuthorString = @"celerih",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/685391/covers/cover.jpg?1524597970",
},
},
},
});
AddStep(@"null max participants", () => room.MaxParticipants.Value = null);
AddStep(@"change participants", () => room.Participants.Value = new[]
{
new User
{
Username = @"Spectator",
Id = 702598,
Country = new Country { FlagName = @"KR" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/702598/3bbf4cb8b8d2cf8b03145000a975ff27e191ab99b0920832e7dd67386280e288.jpeg",
IsSupporter = true,
},
new User
{
Username = @"celerih",
Id = 4696296,
Country = new Country { FlagName = @"CA" },
CoverUrl = @"https://assets.ppy.sh/user-profile-covers/4696296/7f8500731d0ac66d5472569d146a7be07d9460273361913f22c038867baddaef.jpeg",
},
});
AddStep(@"exit", match.Exit);
}
}
}

View File

@ -1,43 +1,54 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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 NUnit.Framework; using System;
using System.Collections.Generic;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Screens.Multi.Screens.Match; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.GameTypes;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Mods;
using osu.Game.Screens.Multi.Match.Components;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[TestFixture]
public class TestCaseMatchHeader : OsuTestCase public class TestCaseMatchHeader : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Header)
};
public TestCaseMatchHeader() public TestCaseMatchHeader()
{ {
Header header = new Header(); var room = new Room();
Add(header);
AddStep(@"set beatmap set", () => header.BeatmapSet = new BeatmapSetInfo var header = new Header(room);
room.Playlist.Add(new PlaylistItem
{ {
OnlineInfo = new BeatmapSetOnlineInfo Beatmap = new BeatmapInfo
{ {
Covers = new BeatmapSetOnlineCovers Metadata = new BeatmapMetadata
{ {
Cover = @"https://assets.ppy.sh/beatmaps/760757/covers/cover.jpg?1526944540", Title = "Title",
Artist = "Artist",
AuthorString = "Author",
}, },
Version = "Version",
Ruleset = new OsuRuleset().RulesetInfo
}, },
RequiredMods =
{
new OsuModDoubleTime(),
new OsuModNoFail(),
new OsuModRelax(),
}
}); });
AddStep(@"change beatmap set", () => header.BeatmapSet = new BeatmapSetInfo room.Type.Value = new GameTypeTimeshift();
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/761883/covers/cover.jpg?1525557400",
},
},
});
AddStep(@"null beatmap set", () => header.BeatmapSet = null); Child = header;
} }
} }
} }

View File

@ -0,0 +1,35 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
public class TestCaseMatchHostInfo : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(HostInfo)
};
private readonly Bindable<User> host = new Bindable<User>(new User { Username = "SomeHost" });
public TestCaseMatchHostInfo()
{
HostInfo hostInfo;
Child = hostInfo = new HostInfo
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre
};
hostInfo.Host.BindTo(host);
}
}
}

View File

@ -1,56 +1,80 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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 System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.RoomStatuses;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Screens.Match; using osu.Game.Screens.Multi.Match.Components;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[TestFixture] [TestFixture]
public class TestCaseMatchInfo : OsuTestCase public class TestCaseMatchInfo : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Info),
typeof(HeaderButton),
typeof(ReadyButton),
typeof(ViewBeatmapButton)
};
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(RulesetStore rulesets) private void load(RulesetStore rulesets)
{ {
Info info = new Info(); var room = new Room();
Info info = new Info(room);
Add(info); Add(info);
AddStep(@"set name", () => info.Name = @"Room Name?"); AddStep(@"set name", () => room.Name.Value = @"Room Name?");
AddStep(@"set availability", () => info.Availability = RoomAvailability.FriendsOnly); AddStep(@"set availability", () => room.Availability.Value = RoomAvailability.FriendsOnly);
AddStep(@"set status", () => info.Status = new RoomStatusPlaying()); AddStep(@"set status", () => room.Status.Value = new RoomStatusPlaying());
AddStep(@"set beatmap", () => info.Beatmap = new BeatmapInfo AddStep(@"set beatmap", () =>
{ {
StarDifficulty = 2.4, room.Playlist.Clear();
Ruleset = rulesets.GetRuleset(0), room.Playlist.Add(new PlaylistItem
Metadata = new BeatmapMetadata
{ {
Title = @"My Song", Beatmap = new BeatmapInfo
Artist = @"VisualTests", {
AuthorString = @"osu!lazer", StarDifficulty = 2.4,
}, Ruleset = rulesets.GetRuleset(0),
Metadata = new BeatmapMetadata
{
Title = @"My Song",
Artist = @"VisualTests",
AuthorString = @"osu!lazer",
},
}
});
}); });
AddStep(@"set type", () => info.Type = new GameTypeTagTeam()); AddStep(@"change name", () => room.Name.Value = @"Room Name!");
AddStep(@"change availability", () => room.Availability.Value = RoomAvailability.InviteOnly);
AddStep(@"change name", () => info.Name = @"Room Name!"); AddStep(@"change status", () => room.Status.Value = new RoomStatusOpen());
AddStep(@"change availability", () => info.Availability = RoomAvailability.InviteOnly); AddStep(@"null beatmap", () => room.Playlist.Clear());
AddStep(@"change status", () => info.Status = new RoomStatusOpen()); AddStep(@"change beatmap", () =>
AddStep(@"null beatmap", () => info.Beatmap = null);
AddStep(@"change type", () => info.Type = new GameTypeTeamVersus());
AddStep(@"change beatmap", () => info.Beatmap = new BeatmapInfo
{ {
StarDifficulty = 4.2, room.Playlist.Clear();
Ruleset = rulesets.GetRuleset(3), room.Playlist.Add(new PlaylistItem
Metadata = new BeatmapMetadata
{ {
Title = @"Your Song", Beatmap = new BeatmapInfo
Artist = @"Tester", {
AuthorString = @"Someone", StarDifficulty = 4.2,
}, Ruleset = rulesets.GetRuleset(3),
Metadata = new BeatmapMetadata
{
Title = @"Your Song",
Artist = @"Tester",
AuthorString = @"Someone",
},
}
});
}); });
} }
} }

View File

@ -0,0 +1,68 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using Newtonsoft.Json;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Online.API;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Tests.Visual
{
public class TestCaseMatchLeaderboard : OsuTestCase
{
public TestCaseMatchLeaderboard()
{
Add(new MatchLeaderboard(new Room { RoomID = { Value = 3 } })
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Size = new Vector2(550f, 450f),
Scope = MatchLeaderboardScope.Overall,
});
}
[Resolved]
private APIAccess api { get; set; }
[BackgroundDependencyLoader]
private void load()
{
var req = new GetRoomScoresRequest();
req.Success += v => { };
req.Failure += _ => { };
api.Queue(req);
}
private class GetRoomScoresRequest : APIRequest<List<RoomScore>>
{
protected override string Target => "rooms/3/leaderboard";
}
private class RoomScore
{
[JsonProperty("user")]
public User User { get; set; }
[JsonProperty("accuracy")]
public double Accuracy { get; set; }
[JsonProperty("total_score")]
public int TotalScore { get; set; }
[JsonProperty("pp")]
public double PP { get; set; }
[JsonProperty("attempts")]
public int TotalAttempts { get; set; }
[JsonProperty("completed")]
public int CompletedAttempts { get; set; }
}
}
}

View File

@ -1,9 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Screens.Multi.Screens.Match; using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
@ -11,16 +13,20 @@ namespace osu.Game.Tests.Visual
[TestFixture] [TestFixture]
public class TestCaseMatchParticipants : OsuTestCase public class TestCaseMatchParticipants : OsuTestCase
{ {
private readonly Bindable<int?> maxParticipants = new Bindable<int?>();
private readonly Bindable<IEnumerable<User>> users = new Bindable<IEnumerable<User>>();
public TestCaseMatchParticipants() public TestCaseMatchParticipants()
{ {
Participants participants; Participants participants;
Add(participants = new Participants
{
RelativeSizeAxes = Axes.Both,
});
AddStep(@"set max to null", () => participants.Max = null); Add(participants = new Participants { RelativeSizeAxes = Axes.Both });
AddStep(@"set users", () => participants.Users = new[]
participants.MaxParticipants.BindTo(maxParticipants);
participants.Users.BindTo(users);
AddStep(@"set max to null", () => maxParticipants.Value = null);
AddStep(@"set users", () => users.Value = new[]
{ {
new User new User
{ {
@ -48,9 +54,9 @@ namespace osu.Game.Tests.Visual
}, },
}); });
AddStep(@"set max", () => participants.Max = 10); AddStep(@"set max", () => maxParticipants.Value = 10);
AddStep(@"clear users", () => participants.Users = new User[] { }); AddStep(@"clear users", () => users.Value = new User[] { });
AddStep(@"set max to null", () => participants.Max = null); AddStep(@"set max to null", () => maxParticipants.Value = null);
} }
} }
} }

View File

@ -0,0 +1,123 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
using osu.Game.Scoring;
using osu.Game.Screens.Multi.Match.Components;
using osu.Game.Screens.Multi.Ranking;
using osu.Game.Screens.Multi.Ranking.Pages;
using osu.Game.Screens.Multi.Ranking.Types;
using osu.Game.Screens.Ranking;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
public class TestCaseMatchResults : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MatchResults),
typeof(RoomLeaderboardPageInfo),
typeof(RoomLeaderboardPage)
};
[Resolved]
private BeatmapManager beatmaps { get; set; }
[BackgroundDependencyLoader]
private void load()
{
var beatmapInfo = beatmaps.QueryBeatmap(b => b.RulesetID == 0);
if (beatmapInfo != null)
Beatmap.Value = beatmaps.GetWorkingBeatmap(beatmapInfo);
Child = new TestMatchResults(new ScoreInfo
{
User = new User { Id = 10 },
});
}
private class TestMatchResults : MatchResults
{
private readonly Room room;
public TestMatchResults(ScoreInfo score)
: this(score, new Room
{
RoomID = { Value = 1 },
Name = { Value = "an awesome room" }
})
{
}
public TestMatchResults(ScoreInfo score, Room room)
: base(score, room)
{
this.room = room;
}
protected override IEnumerable<IResultPageInfo> CreateResultPages() => new[] { new TestRoomLeaderboardPageInfo(Score, Beatmap, room) };
}
private class TestRoomLeaderboardPageInfo : RoomLeaderboardPageInfo
{
private readonly ScoreInfo score;
private readonly WorkingBeatmap beatmap;
private readonly Room room;
public TestRoomLeaderboardPageInfo(ScoreInfo score, WorkingBeatmap beatmap, Room room)
: base(score, beatmap, room)
{
this.score = score;
this.beatmap = beatmap;
this.room = room;
}
public override ResultsPage CreatePage() => new TestRoomLeaderboardPage(score, beatmap, room);
}
private class TestRoomLeaderboardPage : RoomLeaderboardPage
{
public TestRoomLeaderboardPage(ScoreInfo score, WorkingBeatmap beatmap, Room room)
: base(score, beatmap, room)
{
}
protected override MatchLeaderboard CreateLeaderboard(Room room) => new TestMatchLeaderboard(room);
}
private class TestMatchLeaderboard : RoomLeaderboardPage.ResultsMatchLeaderboard
{
public TestMatchLeaderboard(Room room)
: base(room)
{
}
protected override APIRequest FetchScores(Action<IEnumerable<APIRoomScoreInfo>> scoresCallback)
{
var scores = Enumerable.Range(0, 50).Select(createRoomScore).ToArray();
scoresCallback?.Invoke(scores);
ScoresLoaded?.Invoke(scores);
return null;
}
private APIRoomScoreInfo createRoomScore(int id) => new APIRoomScoreInfo
{
User = new User { Id = id, Username = $"User {id}" },
Accuracy = 0.98,
TotalScore = 987654,
TotalAttempts = 13,
CompletedBeatmaps = 5
};
}
}
}

View File

@ -0,0 +1,161 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi;
using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Screens.Multi.Match.Components;
namespace osu.Game.Tests.Visual
{
public class TestCaseMatchSettingsOverlay : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MatchSettingsOverlay)
};
[Cached(Type = typeof(IRoomManager))]
private TestRoomManager roomManager = new TestRoomManager();
private Room room;
private TestRoomSettings settings;
[SetUp]
public void Setup() => Schedule(() =>
{
room = new Room();
settings = new TestRoomSettings(room)
{
RelativeSizeAxes = Axes.Both,
State = Visibility.Visible
};
Child = settings;
});
[Test]
public void TestButtonEnabledOnlyWithNameAndBeatmap()
{
AddStep("clear name and beatmap", () =>
{
room.Name.Value = "";
room.Playlist.Clear();
});
AddAssert("button disabled", () => !settings.ApplyButton.Enabled);
AddStep("set name", () => room.Name.Value = "Room name");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled);
AddStep("set beatmap", () => room.Playlist.Add(new PlaylistItem { Beatmap = new DummyWorkingBeatmap().BeatmapInfo }));
AddAssert("button enabled", () => settings.ApplyButton.Enabled);
AddStep("clear name", () => room.Name.Value = "");
AddAssert("button disabled", () => !settings.ApplyButton.Enabled);
}
[Test]
public void TestCorrectSettingsApplied()
{
const string expected_name = "expected name";
TimeSpan expectedDuration = TimeSpan.FromMinutes(15);
Room createdRoom = null;
AddStep("setup", () =>
{
settings.NameField.Current.Value = expected_name;
settings.DurationField.Current.Value = expectedDuration;
roomManager.CreateRequested = r =>
{
createdRoom = r;
return true;
};
});
AddStep("create room", () => settings.ApplyButton.Action.Invoke());
AddAssert("has correct name", () => createdRoom.Name.Value == expected_name);
AddAssert("has correct duration", () => createdRoom.Duration.Value == expectedDuration);
}
[Test]
public void TestCreationFailureDisplaysError()
{
bool fail;
AddStep("setup", () =>
{
fail = true;
roomManager.CreateRequested = _ => !fail;
});
AddAssert("error not displayed", () => !settings.ErrorText.IsPresent);
AddStep("create room", () => settings.ApplyButton.Action.Invoke());
AddAssert("error displayed", () => settings.ErrorText.IsPresent);
AddAssert("error has correct text", () => settings.ErrorText.Text == TestRoomManager.FAILED_TEXT);
AddStep("create room no fail", () =>
{
fail = false;
settings.ApplyButton.Action.Invoke();
});
AddUntilStep(() => !settings.ErrorText.IsPresent, "error not displayed");
}
private class TestRoomSettings : MatchSettingsOverlay
{
public new TriangleButton ApplyButton => base.ApplyButton;
public new OsuTextBox NameField => base.NameField;
public new OsuDropdown<TimeSpan> DurationField => base.DurationField;
public new OsuSpriteText ErrorText => base.ErrorText;
public TestRoomSettings(Room room)
: base(room)
{
}
}
private class TestRoomManager : IRoomManager
{
public const string FAILED_TEXT = "failed";
public Func<Room, bool> CreateRequested;
public event Action RoomsUpdated;
public IBindableCollection<Room> Rooms { get; } = null;
public void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null)
{
if (CreateRequested == null)
return;
if (!CreateRequested.Invoke(room))
onError?.Invoke(FAILED_TEXT);
else
onSuccess?.Invoke(room);
}
public void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null) => throw new NotImplementedException();
public void PartRoom() => throw new NotImplementedException();
public void Filter(FilterCriteria criteria) => throw new NotImplementedException();
}
}
}

View File

@ -3,8 +3,8 @@
using NUnit.Framework; using NUnit.Framework;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Screens;
using osu.Game.Screens.Multi; using osu.Game.Screens.Multi;
using osu.Game.Screens.Multi.Screens.Lounge;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
@ -13,15 +13,31 @@ namespace osu.Game.Tests.Visual
{ {
public TestCaseMultiHeader() public TestCaseMultiHeader()
{ {
Lounge lounge; int index = 0;
OsuScreen currentScreen = new TestMultiplayerSubScreen(index);
Children = new Drawable[] Children = new Drawable[]
{ {
lounge = new Lounge currentScreen,
{ new Header(currentScreen)
Padding = new MarginPadding { Top = Header.HEIGHT },
},
new Header(lounge),
}; };
AddStep("push multi screen", () => currentScreen.Push(currentScreen = new TestMultiplayerSubScreen(++index)));
}
private class TestMultiplayerSubScreen : OsuScreen, IMultiplayerSubScreen
{
private readonly int index;
public string ShortTitle => $"Screen {index}";
public TestMultiplayerSubScreen(int index)
{
this.index = index;
}
public override string ToString() => ShortTitle;
} }
} }
} }

View File

@ -1,14 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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 System.Collections.Generic;
using NUnit.Framework; using NUnit.Framework;
using osu.Game.Screens.Multi; using osu.Game.Screens.Multi;
using osu.Game.Screens.Multi.Lounge;
using osu.Game.Screens.Multi.Lounge.Components;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
{ {
[TestFixture] [TestFixture]
public class TestCaseMultiScreen : OsuTestCase public class TestCaseMultiScreen : OsuTestCase
{ {
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(Multiplayer),
typeof(LoungeSubScreen),
typeof(FilterControl)
};
public TestCaseMultiScreen() public TestCaseMultiScreen()
{ {
Multiplayer multi = new Multiplayer(); Multiplayer multi = new Multiplayer();

View File

@ -87,10 +87,7 @@ namespace osu.Game.Tests.Visual
usage.Migrate(); usage.Migrate();
Dependencies.Cache(rulesets = new RulesetStore(factory)); Dependencies.Cache(rulesets = new RulesetStore(factory));
Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null) Dependencies.Cache(manager = new BeatmapManager(LocalStorage, factory, rulesets, null, null, null, defaultBeatmap = Beatmap.Default));
{
DefaultBeatmap = defaultBeatmap = Beatmap.Default
});
Beatmap.SetDefault(); Beatmap.SetDefault();
} }

View File

@ -17,7 +17,7 @@ namespace osu.Game.Tests.Visual
{ {
Beatmap.Value = new DummyWorkingBeatmap(game); Beatmap.Value = new DummyWorkingBeatmap(game);
AddStep("load dummy beatmap", () => Add(loader = new PlayerLoader(new Player AddStep("load dummy beatmap", () => Add(loader = new PlayerLoader(() => new Player
{ {
AllowPause = false, AllowPause = false,
AllowLeadIn = false, AllowLeadIn = false,
@ -30,9 +30,9 @@ namespace osu.Game.Tests.Visual
AddStep("load slow dummy beatmap", () => AddStep("load slow dummy beatmap", () =>
{ {
SlowLoadPlayer slow; SlowLoadPlayer slow = null;
Add(loader = new PlayerLoader(slow = new SlowLoadPlayer Add(loader = new PlayerLoader(() => slow = new SlowLoadPlayer
{ {
AllowPause = false, AllowPause = false,
AllowLeadIn = false, AllowLeadIn = false,

View File

@ -5,7 +5,6 @@ using System.ComponentModel;
using System.Linq; using System.Linq;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods; using osu.Game.Rulesets.Mods;
using osu.Game.Scoring;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
namespace osu.Game.Tests.Visual namespace osu.Game.Tests.Visual
@ -23,7 +22,7 @@ namespace osu.Game.Tests.Visual
// Reset the mods // Reset the mods
Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay)); Beatmap.Value.Mods.Value = Beatmap.Value.Mods.Value.Where(m => !(m is ModAutoplay));
return new ReplayPlayer(new Score { Replay = dummyRulesetContainer.Replay }); return new ReplayPlayer(dummyRulesetContainer.ReplayScore);
} }
} }
} }

View File

@ -26,7 +26,7 @@ namespace osu.Game.Tests.Visual
typeof(Results), typeof(Results),
typeof(ResultsPage), typeof(ResultsPage),
typeof(ScoreResultsPage), typeof(ScoreResultsPage),
typeof(RankingResultsPage) typeof(LocalLeaderboardPage)
}; };
[BackgroundDependencyLoader] [BackgroundDependencyLoader]

View File

@ -1,147 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osu.Game.Screens.Multi.Components;
using osu.Game.Users;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseRoomInspector : OsuTestCase
{
private RulesetStore rulesets;
protected override void LoadComplete()
{
base.LoadComplete();
Room room = new Room
{
Name = { Value = @"My Awesome Room" },
Host = { Value = new User { Username = @"flyte", Id = 3103765, Country = new Country { FlagName = @"JP" } } },
Status = { Value = new RoomStatusOpen() },
Type = { Value = new GameTypeTeamVersus() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 3.7,
Ruleset = rulesets.GetRuleset(3),
Metadata = new BeatmapMetadata
{
Title = @"Platina",
Artist = @"Maaya Sakamoto",
AuthorString = @"uwutm8",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/560573/covers/cover.jpg?1492722343",
},
},
},
}
},
MaxParticipants = { Value = 200 },
Participants =
{
Value = new[]
{
new User { Username = @"flyte", Id = 3103765, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 142 } } },
new User { Username = @"Cookiezi", Id = 124493, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 546 } } },
new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 287 } } },
new User { Username = @"Rafis", Id = 2558286, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 468 } } },
new User { Username = @"hvick225", Id = 50265, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 325 } } },
new User { Username = @"peppy", Id = 2, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 625 } } },
}
}
};
RoomInspector inspector;
Add(inspector = new RoomInspector
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
});
AddStep(@"set room", () => inspector.Room = room);
AddStep(@"null room", () => inspector.Room = null);
AddStep(@"set room", () => inspector.Room = room);
AddStep(@"change title", () => room.Name.Value = @"A Better Room Than The Above");
AddStep(@"change host", () => room.Host.Value = new User { Username = @"DrabWeb", Id = 6946022, Country = new Country { FlagName = @"CA" } });
AddStep(@"change status", () => room.Status.Value = new RoomStatusPlaying());
AddStep(@"change type", () => room.Type.Value = new GameTypeTag());
AddStep(@"change beatmap", () => room.Beatmap.Value = null);
AddStep(@"change max participants", () => room.MaxParticipants.Value = null);
AddStep(@"change participants", () => room.Participants.Value = new[]
{
new User { Username = @"filsdelama", Id = 2831793, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 854 } } },
new User { Username = @"_index", Id = 652457, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 150 } } }
});
AddStep(@"change room", () =>
{
Room newRoom = new Room
{
Name = { Value = @"My New, Better Than Ever Room" },
Host = { Value = new User { Username = @"Angelsim", Id = 1777162, Country = new Country { FlagName = @"KR" } } },
Status = { Value = new RoomStatusOpen() },
Type = { Value = new GameTypeTagTeam() },
Beatmap =
{
Value = new BeatmapInfo
{
StarDifficulty = 7.07,
Ruleset = rulesets.GetRuleset(0),
Metadata = new BeatmapMetadata
{
Title = @"FREEDOM DIVE",
Artist = @"xi",
AuthorString = @"Nakagawa-Kanon",
},
BeatmapSet = new BeatmapSetInfo
{
OnlineInfo = new BeatmapSetOnlineInfo
{
Covers = new BeatmapSetOnlineCovers
{
Cover = @"https://assets.ppy.sh/beatmaps/39804/covers/cover.jpg?1456506845",
},
},
},
},
},
MaxParticipants = { Value = 10 },
Participants =
{
Value = new[]
{
new User { Username = @"Angelsim", Id = 1777162, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 4 } } },
new User { Username = @"HappyStick", Id = 256802, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 752 } } },
new User { Username = @"-Konpaku-", Id = 2258797, Statistics = new UserStatistics { Ranks = new UserStatistics.UserRanks { Global = 571 } } }
}
}
};
inspector.Room = newRoom;
});
}
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
this.rulesets = rulesets;
}
}
}

View File

@ -1,119 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Testing.Input;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Screens.Match.Settings;
using osuTK.Input;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseRoomSettings : ManualInputManagerTestCase
{
private readonly Room room;
private readonly TestRoomSettingsOverlay overlay;
public TestCaseRoomSettings()
{
room = new Room
{
Name = { Value = "One Testing Room" },
Availability = { Value = RoomAvailability.Public },
Type = { Value = new GameTypeTeamVersus() },
MaxParticipants = { Value = 10 },
};
Add(overlay = new TestRoomSettingsOverlay(room)
{
RelativeSizeAxes = Axes.Both,
Height = 0.75f,
});
AddStep(@"show", overlay.Show);
assertAll();
AddStep(@"set name", () => overlay.CurrentName = @"Two Testing Room");
AddStep(@"set max", () => overlay.CurrentMaxParticipants = null);
AddStep(@"set availability", () => overlay.CurrentAvailability = RoomAvailability.InviteOnly);
AddStep(@"set type", () => overlay.CurrentType = new GameTypeTagTeam());
apply();
assertAll();
AddStep(@"show", overlay.Show);
AddStep(@"set room name", () => room.Name.Value = @"Room Changed Name!");
AddStep(@"set room availability", () => room.Availability.Value = RoomAvailability.Public);
AddStep(@"set room type", () => room.Type.Value = new GameTypeTag());
AddStep(@"set room max", () => room.MaxParticipants.Value = 100);
assertAll();
AddStep(@"set name", () => overlay.CurrentName = @"Unsaved Testing Room");
AddStep(@"set max", () => overlay.CurrentMaxParticipants = 20);
AddStep(@"set availability", () => overlay.CurrentAvailability = RoomAvailability.FriendsOnly);
AddStep(@"set type", () => overlay.CurrentType = new GameTypeVersus());
AddStep(@"hide", overlay.Hide);
AddWaitStep(5);
AddStep(@"show", overlay.Show);
assertAll();
AddStep(@"hide", overlay.Hide);
}
private void apply()
{
AddStep(@"apply", () =>
{
overlay.ClickApplyButton(InputManager);
});
}
private void assertAll()
{
AddAssert(@"name == room name", () => overlay.CurrentName == room.Name.Value);
AddAssert(@"max == room max", () => overlay.CurrentMaxParticipants == room.MaxParticipants.Value);
AddAssert(@"availability == room availability", () => overlay.CurrentAvailability == room.Availability.Value);
AddAssert(@"type == room type", () => Equals(overlay.CurrentType, room.Type.Value));
}
private class TestRoomSettingsOverlay : RoomSettingsOverlay
{
public string CurrentName
{
get => NameField.Text;
set => NameField.Text = value;
}
public int? CurrentMaxParticipants
{
get
{
if (int.TryParse(MaxParticipantsField.Text, out int max))
return max;
return null;
}
set => MaxParticipantsField.Text = value?.ToString();
}
public RoomAvailability CurrentAvailability
{
get => AvailabilityPicker.Current.Value;
set => AvailabilityPicker.Current.Value = value;
}
public GameType CurrentType
{
get => TypePicker.Current.Value;
set => TypePicker.Current.Value = value;
}
public TestRoomSettingsOverlay(Room room) : base(room)
{
}
public void ClickApplyButton(ManualInputManager inputManager)
{
inputManager.MoveMouseTo(ApplyButton);
inputManager.Click(MouseButton.Left);
}
}
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.RoomStatuses;
using osu.Game.Screens.Multi.Lounge.Components;
namespace osu.Game.Tests.Visual
{
public class TestCaseRoomStatus : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(RoomStatusEnded),
typeof(RoomStatusOpen),
typeof(RoomStatusPlaying)
};
public TestCaseRoomStatus()
{
Child = new FillFlowContainer
{
RelativeSizeAxes = Axes.Both,
Width = 0.5f,
Children = new Drawable[]
{
new DrawableRoom(new Room
{
Name = { Value = "Room 1" },
Status = { Value = new RoomStatusOpen() }
}),
new DrawableRoom(new Room
{
Name = { Value = "Room 2" },
Status = { Value = new RoomStatusPlaying() }
}),
new DrawableRoom(new Room
{
Name = { Value = "Room 3" },
Status = { Value = new RoomStatusEnded() }
}),
}
};
}
}
}

View File

@ -0,0 +1,57 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Rulesets;
using osu.Game.Tests.Beatmaps.IO;
namespace osu.Game.Tests.Visual
{
public class TestCaseUpdateableBeatmapBackgroundSprite : OsuTestCase
{
private UpdateableBeatmapBackgroundSprite backgroundSprite;
[Resolved]
private BeatmapManager beatmaps { get; set; }
[BackgroundDependencyLoader]
private void load(OsuGameBase osu, APIAccess api, RulesetStore rulesets)
{
Bindable<BeatmapInfo> beatmapBindable = new Bindable<BeatmapInfo>();
var imported = ImportBeatmapTest.LoadOszIntoOsu(osu);
Child = backgroundSprite = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both };
backgroundSprite.Beatmap.BindTo(beatmapBindable);
var req = new GetBeatmapSetRequest(1);
api.Queue(req);
AddStep("null", () => beatmapBindable.Value = null);
AddStep("imported", () => beatmapBindable.Value = imported.Beatmaps.First());
if (api.IsLoggedIn)
{
AddUntilStep(() => req.Result != null, "wait for api response");
AddStep("online", () => beatmapBindable.Value = new BeatmapInfo
{
BeatmapSet = req.Result?.ToBeatmapSet(rulesets)
});
}
else
{
AddStep("online (login first)", () => { });
}
}
}
}

View File

@ -59,7 +59,7 @@ namespace osu.Game.Beatmaps
/// <summary> /// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available. /// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary> /// </summary>
public WorkingBeatmap DefaultBeatmap { private get; set; } public readonly WorkingBeatmap DefaultBeatmap;
public override string[] HandledExtensions => new[] { ".osz" }; public override string[] HandledExtensions => new[] { ".osz" };
@ -77,16 +77,19 @@ namespace osu.Game.Beatmaps
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>(); private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null) public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null,
WorkingBeatmap defaultBeatmap = null)
: base(storage, contextFactory, new BeatmapStore(contextFactory), importHost) : base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
{ {
beatmaps = (BeatmapStore)ModelStore;
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
this.rulesets = rulesets; this.rulesets = rulesets;
this.api = api; this.api = api;
this.audioManager = audioManager; this.audioManager = audioManager;
DefaultBeatmap = defaultBeatmap;
beatmaps = (BeatmapStore)ModelStore;
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
} }
protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive) protected override void Populate(BeatmapSetInfo beatmapSet, ArchiveReader archive)

View File

@ -23,7 +23,7 @@ namespace osu.Game.Beatmaps.Drawables
} }
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(TextureStore textures) private void load(LargeTextureStore textures)
{ {
string resource = null; string resource = null;

View File

@ -9,6 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Rulesets;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -16,15 +17,16 @@ namespace osu.Game.Beatmaps.Drawables
{ {
public class DifficultyIcon : DifficultyColouredContainer public class DifficultyIcon : DifficultyColouredContainer
{ {
private readonly BeatmapInfo beatmap; private readonly RulesetInfo ruleset;
public DifficultyIcon(BeatmapInfo beatmap) public DifficultyIcon(BeatmapInfo beatmap, RulesetInfo ruleset = null)
: base(beatmap) : base(beatmap)
{ {
if (beatmap == null) if (beatmap == null)
throw new ArgumentNullException(nameof(beatmap)); throw new ArgumentNullException(nameof(beatmap));
this.beatmap = beatmap; this.ruleset = ruleset ?? beatmap.Ruleset;
Size = new Vector2(20); Size = new Vector2(20);
} }
@ -58,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables
Origin = Anchor.Centre, Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
// the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment) // the null coalesce here is only present to make unit tests work (ruleset dlls aren't copied correctly for testing at the moment)
Icon = beatmap.Ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o } Icon = ruleset?.CreateInstance().CreateIcon() ?? new SpriteIcon { Icon = FontAwesome.fa_question_circle_o }
} }
}; };
} }

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Beatmaps.Drawables
{
/// <summary>
/// Display a baetmap background from a local source, but fallback to online source if not available.
/// </summary>
public class UpdateableBeatmapBackgroundSprite : ModelBackedDrawable<BeatmapInfo>
{
public readonly IBindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
[Resolved]
private BeatmapManager beatmaps { get; set; }
public UpdateableBeatmapBackgroundSprite()
{
Beatmap.BindValueChanged(b => Model = b);
}
protected override Drawable CreateDrawable(BeatmapInfo model)
{
return new DelayedLoadUnloadWrapper(() => {
Drawable drawable;
var localBeatmap = beatmaps.GetWorkingBeatmap(model);
if (localBeatmap.BeatmapInfo.ID == 0 && model?.BeatmapSet?.OnlineInfo != null)
drawable = new BeatmapSetCover(model.BeatmapSet);
else
drawable = new BeatmapBackgroundSprite(localBeatmap);
drawable.RelativeSizeAxes = Axes.Both;
drawable.Anchor = Anchor.Centre;
drawable.Origin = Anchor.Centre;
drawable.FillMode = FillMode.Fill;
drawable.OnLoadComplete = d => d.FadeInFromZero(400);
return drawable;
}, 500, 10000);
}
protected override double FadeDuration => 0;
}
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications; using osu.Game.Overlays.Notifications;
@ -61,11 +62,25 @@ namespace osu.Game.Graphics.Containers
} }
public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action<SpriteText> creationParameters = null) public void AddLink(string text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(AddText(text, creationParameters), text, url, linkType, linkArgument, tooltipText);
public void AddLink(string text, Action action, string tooltipText = null, Action<SpriteText> creationParameters = null)
=> createLink(AddText(text, creationParameters), text, tooltipText: tooltipText, action: action);
public void AddLink(IEnumerable<SpriteText> text, string url, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null)
{ {
AddInternal(new DrawableLinkCompiler(AddText(text, creationParameters).ToList()) foreach (var t in text)
AddArbitraryDrawable(t);
createLink(text, null, url, linkType, linkArgument, tooltipText);
}
private void createLink(IEnumerable<Drawable> drawables, string text, string url = null, LinkAction linkType = LinkAction.External, string linkArgument = null, string tooltipText = null, Action action = null)
{
AddInternal(new DrawableLinkCompiler(drawables.OfType<SpriteText>().ToList())
{ {
TooltipText = tooltipText ?? (url != text ? url : string.Empty), TooltipText = tooltipText ?? (url != text ? url : string.Empty),
Action = () => Action = action ?? (() =>
{ {
switch (linkType) switch (linkType)
{ {
@ -104,7 +119,7 @@ namespace osu.Game.Graphics.Containers
default: default:
throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action."); throw new NotImplementedException($"This {nameof(LinkAction)} ({linkType.ToString()}) is missing an associated action.");
} }
}, }),
}); });
} }
} }

View File

@ -145,7 +145,8 @@ namespace osu.Game.Online.API
if (!handleRequest(userReq)) if (!handleRequest(userReq))
{ {
Thread.Sleep(500); if (State == APIState.Connecting)
State = APIState.Failing;
continue; continue;
} }

View File

@ -36,7 +36,7 @@ namespace osu.Game.Online.API
/// </summary> /// </summary>
public abstract class APIRequest public abstract class APIRequest
{ {
protected virtual string Target => string.Empty; protected abstract string Target { get; }
protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri); protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri);
@ -92,7 +92,11 @@ namespace osu.Game.Online.API
public void Fail(Exception e) public void Fail(Exception e)
{ {
if (cancelled) return; if (WebRequest?.Completed == true)
return;
if (cancelled)
return;
cancelled = true; cancelled = true;
WebRequest?.Abort(); WebRequest?.Abort();

View File

@ -0,0 +1,35 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Net.Http;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Online.Multiplayer;
namespace osu.Game.Online.API.Requests
{
public class CreateRoomRequest : APIRequest<APICreatedRoom>
{
private readonly Room room;
public CreateRoomRequest(Room room)
{
this.room = room;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.ContentType = "application/json";
req.Method = HttpMethod.Post;
req.AddRaw(JsonConvert.SerializeObject(room));
return req;
}
protected override string Target => "rooms";
}
}

View File

@ -0,0 +1,30 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Net.Http;
using osu.Framework.IO.Network;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class CreateRoomScoreRequest : APIRequest<APIScoreToken>
{
private readonly int roomId;
private readonly int playlistItemId;
public CreateRoomScoreRequest(int roomId, int playlistItemId)
{
this.roomId = roomId;
this.playlistItemId = playlistItemId;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Post;
return req;
}
protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores";
}
}

View File

@ -0,0 +1,20 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Online.API.Requests.Responses;
namespace osu.Game.Online.API.Requests
{
public class GetRoomScoresRequest : APIRequest<List<APIRoomScoreInfo>>
{
private readonly int roomId;
public GetRoomScoresRequest(int roomId)
{
this.roomId = roomId;
}
protected override string Target => $@"rooms/{roomId}/leaderboard";
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Lounge.Components;
namespace osu.Game.Online.API.Requests
{
public class GetRoomsRequest : APIRequest<List<Room>>
{
private readonly PrimaryFilter primaryFilter;
public GetRoomsRequest(PrimaryFilter primaryFilter)
{
this.primaryFilter = primaryFilter;
}
protected override string Target
{
get
{
string target = "rooms";
switch (primaryFilter)
{
case PrimaryFilter.Open:
break;
case PrimaryFilter.Owned:
target += "/owned";
break;
case PrimaryFilter.Participated:
target += "/participated";
break;
case PrimaryFilter.RecentlyEnded:
target += "/ended";
break;
}
return target;
}
}
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Net.Http;
using osu.Framework.IO.Network;
using osu.Game.Online.Multiplayer;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests
{
public class JoinRoomRequest : APIRequest
{
private readonly Room room;
private readonly User user;
public JoinRoomRequest(Room room, User user)
{
this.room = room;
this.user = user;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Put;
return req;
}
protected override string Target => $"rooms/{room.RoomID.Value}/users/{user.Id}";
}
}

View File

@ -0,0 +1,31 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Net.Http;
using osu.Framework.IO.Network;
using osu.Game.Online.Multiplayer;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests
{
public class PartRoomRequest : APIRequest
{
private readonly Room room;
private readonly User user;
public PartRoomRequest(Room room, User user)
{
this.room = room;
this.user = user;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Delete;
return req;
}
protected override string Target => $"rooms/{room.RoomID.Value}/users/{user.Id}";
}
}

View File

@ -59,19 +59,17 @@ namespace osu.Game.Online.API.Requests.Responses
public BeatmapInfo ToBeatmap(RulesetStore rulesets) public BeatmapInfo ToBeatmap(RulesetStore rulesets)
{ {
var set = BeatmapSet?.ToBeatmapSet(rulesets);
return new BeatmapInfo return new BeatmapInfo
{ {
Metadata = this, Metadata = set?.Metadata ?? this,
Ruleset = rulesets.GetRuleset(ruleset), Ruleset = rulesets.GetRuleset(ruleset),
StarDifficulty = starDifficulty, StarDifficulty = starDifficulty,
OnlineBeatmapID = OnlineBeatmapID, OnlineBeatmapID = OnlineBeatmapID,
Version = version, Version = version,
Status = Status, Status = Status,
BeatmapSet = new BeatmapSetInfo BeatmapSet = set,
{
OnlineBeatmapSetID = OnlineBeatmapSetID,
Status = BeatmapSet?.Status ?? BeatmapSetOnlineStatus.None
},
BaseDifficulty = new BeatmapDifficulty BaseDifficulty = new BeatmapDifficulty
{ {
DrainRate = drainRate, DrainRate = drainRate,

View File

@ -0,0 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Online.Multiplayer;
namespace osu.Game.Online.API.Requests.Responses
{
public class APICreatedRoom : Room
{
[JsonProperty("error")]
public string Error { get; set; }
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Mods;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIMod : IMod
{
public string Acronym { get; set; }
}
}

View File

@ -0,0 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Scoring;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIRoomScoreInfo : ScoreInfo
{
[JsonProperty("attempts")]
public int TotalAttempts { get; set; }
[JsonProperty("completed")]
public int CompletedBeatmaps { get; set; }
}
}

View File

@ -0,0 +1,13 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
namespace osu.Game.Online.API.Requests.Responses
{
public class APIScoreToken
{
[JsonProperty("id")]
public int ID { get; set; }
}
}

View File

@ -0,0 +1,40 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Net.Http;
using Newtonsoft.Json;
using osu.Framework.IO.Network;
using osu.Game.Scoring;
namespace osu.Game.Online.API.Requests
{
public class SubmitRoomScoreRequest : APIRequest
{
private readonly int scoreId;
private readonly int roomId;
private readonly int playlistItemId;
private readonly ScoreInfo scoreInfo;
public SubmitRoomScoreRequest(int scoreId, int roomId, int playlistItemId, ScoreInfo scoreInfo)
{
this.scoreId = scoreId;
this.roomId = roomId;
this.playlistItemId = playlistItemId;
this.scoreInfo = scoreInfo;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.ContentType = "application/json";
req.Method = HttpMethod.Put;
req.AddRaw(JsonConvert.SerializeObject(scoreInfo));
return req;
}
protected override string Target => $@"rooms/{roomId}/playlist/{playlistItemId}/scores/{scoreId}";
}
}

View File

@ -19,10 +19,10 @@ namespace osu.Game.Online.Chat
/// </summary> /// </summary>
public class StandAloneChatDisplay : CompositeDrawable public class StandAloneChatDisplay : CompositeDrawable
{ {
private readonly bool postingTextbox;
public readonly Bindable<Channel> Channel = new Bindable<Channel>(); public readonly Bindable<Channel> Channel = new Bindable<Channel>();
public Action Exit;
private readonly FocusedTextBox textbox; private readonly FocusedTextBox textbox;
protected ChannelManager ChannelManager; protected ChannelManager ChannelManager;
@ -31,6 +31,8 @@ namespace osu.Game.Online.Chat
private DrawableChannel drawableChannel; private DrawableChannel drawableChannel;
private readonly bool postingTextbox;
private const float textbox_height = 30; private const float textbox_height = 30;
/// <summary> /// <summary>
@ -66,6 +68,8 @@ namespace osu.Game.Online.Chat
Anchor = Anchor.BottomLeft, Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft, Origin = Anchor.BottomLeft,
}); });
textbox.Exit += () => Exit?.Invoke();
} }
Channel.BindValueChanged(channelChanged); Channel.BindValueChanged(channelChanged);

View File

@ -18,7 +18,7 @@ using osuTK.Graphics;
namespace osu.Game.Online.Leaderboards namespace osu.Game.Online.Leaderboards
{ {
public abstract class Leaderboard<TScope, ScoreInfo> : Container public abstract class Leaderboard<TScope, ScoreInfo> : Container, IOnlineComponent
{ {
private const double fade_duration = 300; private const double fade_duration = 300;
@ -55,14 +55,8 @@ namespace osu.Game.Online.Leaderboards
// ensure placeholder is hidden when displaying scores // ensure placeholder is hidden when displaying scores
PlaceholderState = PlaceholderState.Successful; PlaceholderState = PlaceholderState.Successful;
var flow = scrollFlow = new FillFlowContainer<LeaderboardScore> scrollFlow = CreateScoreFlow();
{ scrollFlow.ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1));
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0f, 5f),
Padding = new MarginPadding { Top = 10, Bottom = 5 },
ChildrenEnumerable = scores.Select((s, index) => CreateDrawableScore(s, index + 1))
};
// schedule because we may not be loaded yet (LoadComponentAsync complains). // schedule because we may not be loaded yet (LoadComponentAsync complains).
showScoresDelegate?.Cancel(); showScoresDelegate?.Cancel();
@ -71,12 +65,12 @@ namespace osu.Game.Online.Leaderboards
else else
showScores(); showScores();
void showScores() => LoadComponentAsync(flow, _ => void showScores() => LoadComponentAsync(scrollFlow, _ =>
{ {
scrollContainer.Add(flow); scrollContainer.Add(scrollFlow);
int i = 0; int i = 0;
foreach (var s in flow.Children) foreach (var s in scrollFlow.Children)
{ {
using (s.BeginDelayedSequence(i++ * 50, true)) using (s.BeginDelayedSequence(i++ * 50, true))
s.Show(); s.Show();
@ -87,6 +81,15 @@ namespace osu.Game.Online.Leaderboards
} }
} }
protected virtual FillFlowContainer<LeaderboardScore> CreateScoreFlow()
=> new FillFlowContainer<LeaderboardScore>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Spacing = new Vector2(0f, 5f),
Padding = new MarginPadding { Top = 10, Bottom = 5 },
};
private TScope scope; private TScope scope;
public TScope Scope public TScope Scope
@ -175,26 +178,22 @@ namespace osu.Game.Online.Leaderboards
private void load(APIAccess api) private void load(APIAccess api)
{ {
this.api = api; this.api = api;
api?.Register(this);
if (api != null)
api.OnStateChange += handleApiStateChange;
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
api?.Unregister(this);
if (api != null)
api.OnStateChange -= handleApiStateChange;
} }
public void RefreshScores() => UpdateScores(); public void RefreshScores() => UpdateScores();
private APIRequest getScoresRequest; private APIRequest getScoresRequest;
private void handleApiStateChange(APIState oldState, APIState newState) public void APIStateChanged(APIAccess api, APIState state)
{ {
if (newState == APIState.Online) if (state == APIState.Online)
UpdateScores(); UpdateScores();
} }
@ -265,14 +264,18 @@ namespace osu.Game.Online.Leaderboards
currentPlaceholder = placeholder; currentPlaceholder = placeholder;
} }
protected virtual bool FadeBottom => true;
protected virtual bool FadeTop => false;
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
var fadeStart = scrollContainer.Current + scrollContainer.DrawHeight; var fadeBottom = scrollContainer.Current + scrollContainer.DrawHeight;
var fadeTop = scrollContainer.Current + LeaderboardScore.HEIGHT;
if (!scrollContainer.IsScrolledToEnd()) if (!scrollContainer.IsScrolledToEnd())
fadeStart -= LeaderboardScore.HEIGHT; fadeBottom -= LeaderboardScore.HEIGHT;
if (scrollFlow == null) if (scrollFlow == null)
return; return;
@ -282,15 +285,23 @@ namespace osu.Game.Online.Leaderboards
var topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y; var topY = c.ToSpaceOfOtherDrawable(Vector2.Zero, scrollFlow).Y;
var bottomY = topY + LeaderboardScore.HEIGHT; var bottomY = topY + LeaderboardScore.HEIGHT;
if (bottomY < fadeStart) bool requireTopFade = FadeTop && topY <= fadeTop;
bool requireBottomFade = FadeBottom && bottomY >= fadeBottom;
if (!requireTopFade && !requireBottomFade)
c.Colour = Color4.White; c.Colour = Color4.White;
else if (topY > fadeStart + LeaderboardScore.HEIGHT) else if (topY > fadeBottom + LeaderboardScore.HEIGHT || bottomY < fadeTop - LeaderboardScore.HEIGHT)
c.Colour = Color4.Transparent; c.Colour = Color4.Transparent;
else else
{ {
c.Colour = ColourInfo.GradientVertical( if (bottomY - fadeBottom > 0 && FadeBottom)
Color4.White.Opacity(Math.Min(1 - (topY - fadeStart) / LeaderboardScore.HEIGHT, 1)), c.Colour = ColourInfo.GradientVertical(
Color4.White.Opacity(Math.Min(1 - (bottomY - fadeStart) / LeaderboardScore.HEIGHT, 1))); Color4.White.Opacity(Math.Min(1 - (topY - fadeBottom) / LeaderboardScore.HEIGHT, 1)),
Color4.White.Opacity(Math.Min(1 - (bottomY - fadeBottom) / LeaderboardScore.HEIGHT, 1)));
else if (FadeTop)
c.Colour = ColourInfo.GradientVertical(
Color4.White.Opacity(Math.Min(1 - (fadeTop - topY) / LeaderboardScore.HEIGHT, 1)),
Color4.White.Opacity(Math.Min(1 - (fadeTop - bottomY) / LeaderboardScore.HEIGHT, 1)));
} }
} }
} }

View File

@ -73,8 +73,8 @@ namespace osu.Game.Online.Leaderboards
{ {
new OsuSpriteText new OsuSpriteText
{ {
Anchor = Anchor.CentreLeft, Anchor = Anchor.Centre,
Origin = Anchor.CentreLeft, Origin = Anchor.Centre,
Font = @"Exo2.0-MediumItalic", Font = @"Exo2.0-MediumItalic",
TextSize = 22, TextSize = 22,
// ReSharper disable once ImpureMethodCallOnReadonlyValueField // ReSharper disable once ImpureMethodCallOnReadonlyValueField

View File

@ -1,11 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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 osuTK;
using osuTK.Graphics;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics; using osu.Game.Graphics;
namespace osu.Game.Online.Multiplayer namespace osu.Game.Online.Multiplayer
@ -13,137 +9,10 @@ namespace osu.Game.Online.Multiplayer
public abstract class GameType public abstract class GameType
{ {
public abstract string Name { get; } public abstract string Name { get; }
public abstract Drawable GetIcon(OsuColour colours, float size); public abstract Drawable GetIcon(OsuColour colours, float size);
public override int GetHashCode() => GetType().GetHashCode(); public override int GetHashCode() => GetType().GetHashCode();
public override bool Equals(object obj) => GetType() == obj?.GetType(); public override bool Equals(object obj) => GetType() == obj?.GetType();
} }
public class GameTypeTag : GameType
{
public override string Name => "Tag";
public override Drawable GetIcon(OsuColour colours, float size)
{
return new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_refresh,
Size = new Vector2(size),
Colour = colours.Blue,
Shadow = false,
};
}
}
public class GameTypeVersus : GameType
{
public override string Name => "Versus";
public override Drawable GetIcon(OsuColour colours, float size)
{
return new VersusRow(colours.Blue, colours.Blue, size * 0.6f)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
}
}
public class GameTypeTagTeam : GameType
{
public override string Name => "Tag Team";
public override Drawable GetIcon(OsuColour colours, float size)
{
return new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(2f),
Children = new[]
{
new SpriteIcon
{
Icon = FontAwesome.fa_refresh,
Size = new Vector2(size * 0.75f),
Colour = colours.Blue,
Shadow = false,
},
new SpriteIcon
{
Icon = FontAwesome.fa_refresh,
Size = new Vector2(size * 0.75f),
Colour = colours.Pink,
Shadow = false,
},
},
};
}
}
public class GameTypeTeamVersus : GameType
{
public override string Name => "Team Versus";
public override Drawable GetIcon(OsuColour colours, float size)
{
return new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(2f),
Children = new[]
{
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
},
};
}
}
public class VersusRow : FillFlowContainer
{
public VersusRow(Color4 first, Color4 second, float size)
{
var triangleSize = new Vector2(size);
AutoSizeAxes = Axes.Both;
Spacing = new Vector2(2f, 0f);
Children = new[]
{
new Container
{
Size = triangleSize,
Colour = first,
Children = new[]
{
new EquilateralTriangle
{
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Rotation = 90,
EdgeSmoothness = new Vector2(1f),
},
},
},
new Container
{
Size = triangleSize,
Colour = second,
Children = new[]
{
new EquilateralTriangle
{
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Rotation = -90,
EdgeSmoothness = new Vector2(1f),
},
},
},
};
}
}
} }

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Online.Multiplayer.GameTypes
{
public class GameTypeTag : GameType
{
public override string Name => "Tag";
public override Drawable GetIcon(OsuColour colours, float size)
{
return new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_refresh,
Size = new Vector2(size),
Colour = colours.Blue,
Shadow = false,
};
}
}
}

View File

@ -0,0 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Online.Multiplayer.GameTypes
{
public class GameTypeTagTeam : GameType
{
public override string Name => "Tag Team";
public override Drawable GetIcon(OsuColour colours, float size)
{
return new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(2f),
Children = new[]
{
new SpriteIcon
{
Icon = FontAwesome.fa_refresh,
Size = new Vector2(size * 0.75f),
Colour = colours.Blue,
Shadow = false,
},
new SpriteIcon
{
Icon = FontAwesome.fa_refresh,
Size = new Vector2(size * 0.75f),
Colour = colours.Pink,
Shadow = false,
},
},
};
}
}
}

View File

@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Online.Multiplayer.GameTypes
{
public class GameTypeTeamVersus : GameType
{
public override string Name => "Team Versus";
public override Drawable GetIcon(OsuColour colours, float size)
{
return new FillFlowContainer
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(2f),
Children = new[]
{
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
new VersusRow(colours.Blue, colours.Pink, size * 0.5f),
},
};
}
}
}

View File

@ -0,0 +1,24 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Graphics;
using osuTK;
namespace osu.Game.Online.Multiplayer.GameTypes
{
public class GameTypeTimeshift : GameType
{
public override string Name => "Timeshift";
public override Drawable GetIcon(OsuColour colours, float size) => new SpriteIcon
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_clock_o,
Size = new Vector2(size),
Colour = colours.Blue,
Shadow = false
};
}
}

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Graphics;
namespace osu.Game.Online.Multiplayer.GameTypes
{
public class GameTypeVersus : GameType
{
public override string Name => "Versus";
public override Drawable GetIcon(OsuColour colours, float size)
{
return new VersusRow(colours.Blue, colours.Blue, size * 0.6f)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
};
}
}
}

View File

@ -0,0 +1,55 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Online.Multiplayer.GameTypes
{
public class VersusRow : FillFlowContainer
{
public VersusRow(Color4 first, Color4 second, float size)
{
var triangleSize = new Vector2(size);
AutoSizeAxes = Axes.Both;
Spacing = new Vector2(2f, 0f);
Children = new[]
{
new Container
{
Size = triangleSize,
Colour = first,
Children = new[]
{
new EquilateralTriangle
{
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Rotation = 90,
EdgeSmoothness = new Vector2(1f),
},
},
},
new Container
{
Size = triangleSize,
Colour = second,
Children = new[]
{
new EquilateralTriangle
{
Anchor = Anchor.BottomLeft,
RelativeSizeAxes = Axes.Both,
Rotation = -90,
EdgeSmoothness = new Vector2(1f),
},
},
},
};
}
}
}

View File

@ -0,0 +1,93 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Configuration;
using osu.Game.Beatmaps;
using osu.Game.Online.API.Requests.Responses;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
namespace osu.Game.Online.Multiplayer
{
public class PlaylistItem
{
[JsonProperty("id")]
public int ID { get; set; }
[JsonProperty("beatmap_id")]
public int BeatmapID { get; set; }
[JsonProperty("ruleset_id")]
public int RulesetID { get; set; }
[JsonIgnore]
public BeatmapInfo Beatmap
{
get => beatmap;
set
{
beatmap = value;
BeatmapID = value?.OnlineBeatmapID ?? 0;
}
}
[JsonIgnore]
public RulesetInfo Ruleset { get; set; }
[JsonIgnore]
public readonly BindableCollection<Mod> AllowedMods = new BindableCollection<Mod>();
[JsonIgnore]
public readonly BindableCollection<Mod> RequiredMods = new BindableCollection<Mod>();
[JsonProperty("beatmap")]
private APIBeatmap apiBeatmap { get; set; }
[JsonProperty("allowed_mods")]
private APIMod[] allowedMods
{
get => AllowedMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
set => _allowedMods = value;
}
[JsonProperty("required_mods")]
private APIMod[] requiredMods
{
get => RequiredMods.Select(m => new APIMod { Acronym = m.Acronym }).ToArray();
set => _requiredMods = value;
}
private BeatmapInfo beatmap;
private APIMod[] _allowedMods;
private APIMod[] _requiredMods;
public void MapObjects(BeatmapManager beatmaps, RulesetStore rulesets)
{
// If we don't have an api beatmap, the request occurred as a result of room creation, so we can query the local beatmap instead
// Todo: Is this a bug? Room creation only returns the beatmap ID
Beatmap = apiBeatmap == null ? beatmaps.QueryBeatmap(b => b.OnlineBeatmapID == BeatmapID) : apiBeatmap.ToBeatmap(rulesets);
Ruleset = rulesets.GetRuleset(RulesetID);
if (_allowedMods != null)
{
AllowedMods.Clear();
AllowedMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _allowedMods.Any(m => m.Acronym == mod.Acronym)));
_allowedMods = null;
}
if (_requiredMods != null)
{
RequiredMods.Clear();
RequiredMods.AddRange(Ruleset.CreateInstance().GetAllMods().Where(mod => _requiredMods.Any(m => m.Acronym == mod.Acronym)));
_requiredMods = null;
}
}
public bool ShouldSerializeID() => false;
public bool ShouldSerializeapiBeatmap() => false;
}
}

View File

@ -1,22 +1,120 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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 System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Game.Beatmaps; using osu.Game.Online.Multiplayer.GameTypes;
using osu.Game.Online.Multiplayer.RoomStatuses;
using osu.Game.Users; using osu.Game.Users;
namespace osu.Game.Online.Multiplayer namespace osu.Game.Online.Multiplayer
{ {
public class Room public class Room
{ {
public Bindable<string> Name = new Bindable<string>(); [JsonProperty("id")]
public Bindable<User> Host = new Bindable<User>(); public Bindable<int?> RoomID { get; private set; } = new Bindable<int?>();
public Bindable<RoomStatus> Status = new Bindable<RoomStatus>();
public Bindable<RoomAvailability> Availability = new Bindable<RoomAvailability>(); [JsonProperty("name")]
public Bindable<GameType> Type = new Bindable<GameType>(); public Bindable<string> Name { get; private set; } = new Bindable<string>();
public Bindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
public Bindable<int?> MaxParticipants = new Bindable<int?>(); [JsonProperty("host")]
public Bindable<IEnumerable<User>> Participants = new Bindable<IEnumerable<User>>(); public Bindable<User> Host { get; private set; } = new Bindable<User>();
[JsonProperty("playlist")]
public BindableCollection<PlaylistItem> Playlist { get; set; } = new BindableCollection<PlaylistItem>();
[JsonProperty("channel_id")]
public Bindable<int> ChannelId { get; private set; } = new Bindable<int>();
[JsonIgnore]
public Bindable<TimeSpan> Duration { get; private set; } = new Bindable<TimeSpan>(TimeSpan.FromMinutes(30));
[JsonIgnore]
public Bindable<int?> MaxAttempts { get; private set; } = new Bindable<int?>();
[JsonIgnore]
public Bindable<RoomStatus> Status { get; private set; } = new Bindable<RoomStatus>(new RoomStatusOpen());
[JsonIgnore]
public Bindable<RoomAvailability> Availability { get; private set; } = new Bindable<RoomAvailability>();
[JsonIgnore]
public Bindable<GameType> Type { get; private set; } = new Bindable<GameType>(new GameTypeTimeshift());
[JsonIgnore]
public Bindable<int?> MaxParticipants { get; private set; } = new Bindable<int?>();
[JsonIgnore]
public Bindable<IEnumerable<User>> Participants { get; private set; } = new Bindable<IEnumerable<User>>(Enumerable.Empty<User>());
public Bindable<int> ParticipantCount { get; private set; } = new Bindable<int>();
// todo: TEMPORARY
[JsonProperty("participant_count")]
private int? participantCount
{
get => ParticipantCount;
set => ParticipantCount.Value = value ?? 0;
}
[JsonProperty("duration")]
private int duration
{
get => (int)Duration.Value.TotalMinutes;
set => Duration.Value = TimeSpan.FromMinutes(value);
}
// Only supports retrieval for now
[JsonProperty("ends_at")]
public Bindable<DateTimeOffset> EndDate { get; private set; } = new Bindable<DateTimeOffset>();
// Todo: Find a better way to do this (https://github.com/ppy/osu-framework/issues/1930)
[JsonProperty("max_attempts", DefaultValueHandling = DefaultValueHandling.Ignore)]
private int? maxAttempts
{
get => MaxAttempts;
set => MaxAttempts.Value = value;
}
/// <summary>
/// The position of this <see cref="Room"/> in the list. This is not read from or written to the API.
/// </summary>
[JsonIgnore]
public int Position = -1;
public void CopyFrom(Room other)
{
RoomID.Value = other.RoomID;
Name.Value = other.Name;
if (other.Host.Value != null && Host.Value?.Id != other.Host.Value.Id)
Host.Value = other.Host;
Status.Value = other.Status;
Availability.Value = other.Availability;
Type.Value = other.Type;
MaxParticipants.Value = other.MaxParticipants;
ParticipantCount.Value = other.ParticipantCount.Value;
Participants.Value = other.Participants.Value.ToArray();
EndDate.Value = other.EndDate;
if (DateTimeOffset.Now >= EndDate.Value)
Status.Value = new RoomStatusEnded();
// Todo: Temporary, should only remove/add new items (requires framework changes)
if (Playlist.Count == 0)
Playlist.AddRange(other.Playlist);
else if (other.Playlist.Count > 0)
Playlist.First().ID = other.Playlist.First().ID;
Position = other.Position;
}
public bool ShouldSerializeRoomID() => false;
public bool ShouldSerializeHost() => false;
public bool ShouldSerializeEndDate() => false;
} }
} }

View File

@ -10,17 +10,8 @@ namespace osu.Game.Online.Multiplayer
{ {
public abstract string Message { get; } public abstract string Message { get; }
public abstract Color4 GetAppropriateColour(OsuColour colours); public abstract Color4 GetAppropriateColour(OsuColour colours);
}
public class RoomStatusOpen : RoomStatus public override int GetHashCode() => GetType().GetHashCode();
{ public override bool Equals(object obj) => GetType() == obj?.GetType();
public override string Message => @"Welcoming Players";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
}
public class RoomStatusPlaying : RoomStatus
{
public override string Message => @"Now Playing";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
} }
} }

View File

@ -0,0 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Online.Multiplayer.RoomStatuses
{
public class RoomStatusEnded : RoomStatus
{
public override string Message => @"Ended";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.YellowDarker;
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Online.Multiplayer.RoomStatuses
{
public class RoomStatusOpen : RoomStatus
{
public override string Message => @"Welcoming Players";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.GreenLight;
}
}

View File

@ -0,0 +1,14 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Graphics;
using osuTK.Graphics;
namespace osu.Game.Online.Multiplayer.RoomStatuses
{
public class RoomStatusPlaying : RoomStatus
{
public override string Message => @"Now Playing";
public override Color4 GetAppropriateColour(OsuColour colours) => colours.Purple;
}
}

View File

@ -39,7 +39,7 @@ namespace osu.Game.Online
/// <summary> /// <summary>
/// ///
/// </summary> /// </summary>
/// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops al polling.</param> /// <param name="timeBetweenPolls">The initial time in milliseconds to wait between polls. Setting to zero stops all polling.</param>
protected PollingComponent(double timeBetweenPolls = 0) protected PollingComponent(double timeBetweenPolls = 0)
{ {
TimeBetweenPolls = timeBetweenPolls; TimeBetweenPolls = timeBetweenPolls;
@ -87,13 +87,22 @@ namespace osu.Game.Online
} }
/// <summary> /// <summary>
/// Perform the polling in this method. Call <see cref="pollComplete"/> when done. /// Performs a poll. Implement but do not call this.
/// </summary> /// </summary>
protected virtual Task Poll() protected virtual Task Poll()
{ {
return Task.CompletedTask; return Task.CompletedTask;
} }
/// <summary>
/// Immediately performs a <see cref="Poll"/>.
/// </summary>
public void PollImmediately()
{
lastTimePolled = Time.Current - timeBetweenPolls;
scheduleNextPoll();
}
/// <summary> /// <summary>
/// Call when a poll operation has completed. /// Call when a poll operation has completed.
/// </summary> /// </summary>
@ -103,7 +112,7 @@ namespace osu.Game.Online
pollingActive = false; pollingActive = false;
if (scheduledPoll == null) if (scheduledPoll == null)
scheduleNextPoll(); pollIfNecessary();
} }
private void scheduleNextPoll() private void scheduleNextPoll()

View File

@ -317,7 +317,7 @@ namespace osu.Game
Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap); Beatmap.Value = BeatmapManager.GetWorkingBeatmap(databasedBeatmap);
Beatmap.Value.Mods.Value = databasedScoreInfo.Mods; Beatmap.Value.Mods.Value = databasedScoreInfo.Mods;
currentScreen.Push(new PlayerLoader(new ReplayPlayer(databasedScore))); currentScreen.Push(new PlayerLoader(() => new ReplayPlayer(databasedScore)));
} }
} }
@ -690,10 +690,41 @@ namespace osu.Game
MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false; MenuCursorContainer.CanShowCursor = currentScreen?.CursorVisible ?? false;
} }
private void screenAdded(Screen newScreen) /// <summary>
/// Sets <see cref="Beatmap"/> while ignoring any beatmap.
/// </summary>
/// <param name="beatmap">The beatmap to set.</param>
public void ForcefullySetBeatmap(WorkingBeatmap beatmap)
{
var beatmapDisabled = Beatmap.Disabled;
Beatmap.Disabled = false;
Beatmap.Value = beatmap;
Beatmap.Disabled = beatmapDisabled;
}
/// <summary>
/// Sets <see cref="Ruleset"/> while ignoring any ruleset restrictions.
/// </summary>
/// <param name="beatmap">The beatmap to set.</param>
public void ForcefullySetRuleset(RulesetInfo ruleset)
{
var rulesetDisabled = this.ruleset.Disabled;
this.ruleset.Disabled = false;
this.ruleset.Value = ruleset;
this.ruleset.Disabled = rulesetDisabled;
}
protected virtual void ScreenChanged(OsuScreen current, Screen newScreen)
{ {
currentScreen = (OsuScreen)newScreen; currentScreen = (OsuScreen)newScreen;
Logger.Log($"Screen changed → {currentScreen}"); }
private void screenAdded(Screen newScreen)
{
ScreenChanged(currentScreen, newScreen);
Logger.Log($"Screen changed → {newScreen}");
newScreen.ModePushed += screenAdded; newScreen.ModePushed += screenAdded;
newScreen.Exited += screenRemoved; newScreen.Exited += screenRemoved;
@ -701,7 +732,7 @@ namespace osu.Game
private void screenRemoved(Screen newScreen) private void screenRemoved(Screen newScreen)
{ {
currentScreen = (OsuScreen)newScreen; ScreenChanged(currentScreen, newScreen);
Logger.Log($"Screen changed ← {currentScreen}"); Logger.Log($"Screen changed ← {currentScreen}");
if (newScreen == null) if (newScreen == null)

View File

@ -153,9 +153,12 @@ namespace osu.Game
dependencies.Cache(API); dependencies.Cache(API);
dependencies.CacheAs<IAPIProvider>(API); dependencies.CacheAs<IAPIProvider>(API);
var defaultBeatmap = new DummyWorkingBeatmap(this);
beatmap = new OsuBindableBeatmap(defaultBeatmap, Audio);
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory)); dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage)); dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, API, Audio, Host, defaultBeatmap));
dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host)); dependencies.Cache(ScoreManager = new ScoreManager(RulesetStore, BeatmapManager, Host.Storage, contextFactory, Host));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory)); dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
@ -166,10 +169,6 @@ namespace osu.Game
fileImporters.Add(ScoreManager); fileImporters.Add(ScoreManager);
fileImporters.Add(SkinManager); fileImporters.Add(SkinManager);
var defaultBeatmap = new DummyWorkingBeatmap(this);
beatmap = new OsuBindableBeatmap(defaultBeatmap, Audio);
BeatmapManager.DefaultBeatmap = defaultBeatmap;
// tracks play so loud our samples can't keep up. // tracks play so loud our samples can't keep up.
// this adds a global reduction of track volume for the time being. // this adds a global reduction of track volume for the time being.
Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8)); Audio.Track.AddAdjustment(AdjustableProperty.Volume, new BindableDouble(0.8));
@ -261,7 +260,7 @@ namespace osu.Game
RegisterAudioManager(audioManager); RegisterAudioManager(audioManager);
} }
private OsuBindableBeatmap(WorkingBeatmap defaultValue) public OsuBindableBeatmap(WorkingBeatmap defaultValue)
: base(defaultValue) : base(defaultValue)
{ {
} }

View File

@ -3,10 +3,12 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -110,7 +112,7 @@ namespace osu.Game.Overlays.Music
{ {
sprite.TextSize = 16; sprite.TextSize = 16;
sprite.Font = @"Exo2.0-Regular"; sprite.Font = @"Exo2.0-Regular";
}); }).OfType<SpriteText>();
text.AddText(artistBind.Value, sprite => text.AddText(artistBind.Value, sprite =>
{ {

View File

@ -17,6 +17,7 @@ using osu.Game.Graphics;
using osuTK.Graphics; using osuTK.Graphics;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics.Containers;
using RectangleF = osu.Framework.Graphics.Primitives.RectangleF; using RectangleF = osu.Framework.Graphics.Primitives.RectangleF;
using Container = osu.Framework.Graphics.Containers.Container; using Container = osu.Framework.Graphics.Containers.Container;
@ -86,25 +87,29 @@ namespace osu.Game.Overlays.Settings.Sections.General
}; };
break; break;
case APIState.Failing: case APIState.Failing:
Children = new Drawable[]
{
new OsuSpriteText
{
Text = "Connection failing :(",
},
};
break;
case APIState.Connecting: case APIState.Connecting:
LinkFlowContainer linkFlow;
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuSpriteText new LoadingAnimation
{ {
Anchor = Anchor.Centre, State = Visibility.Visible,
Origin = Anchor.Centre, Anchor = Anchor.TopCentre,
Text = "Connecting...", Origin = Anchor.TopCentre,
},
linkFlow = new LinkFlowContainer
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
TextAnchor = Anchor.TopCentre,
AutoSizeAxes = Axes.Both,
Text = state == APIState.Failing ? "Connection is failing, will attempt to reconnect... " : "Attempting to connect... ",
Margin = new MarginPadding { Top = 10, Bottom = 10 }, Margin = new MarginPadding { Top = 10, Bottom = 10 },
}, },
}; };
linkFlow.AddLink("cancel", api.Logout, string.Empty);
break; break;
case APIState.Online: case APIState.Online:
Children = new Drawable[] Children = new Drawable[]

View File

@ -1,6 +1,8 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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 Newtonsoft.Json;
namespace osu.Game.Rulesets.Mods namespace osu.Game.Rulesets.Mods
{ {
public interface IMod public interface IMod
@ -8,6 +10,7 @@ namespace osu.Game.Rulesets.Mods
/// <summary> /// <summary>
/// The shortened name of this mod. /// The shortened name of this mod.
/// </summary> /// </summary>
[JsonProperty("acronym")]
string Acronym { get; } string Acronym { get; }
} }
} }

View File

@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Mods
public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0; public override bool HasImplementation => GetType().GenericTypeArguments.Length == 0;
public virtual void ApplyToRulesetContainer(RulesetContainer<T> rulesetContainer) => rulesetContainer.SetReplay(CreateReplayScore(rulesetContainer.Beatmap)?.Replay); public virtual void ApplyToRulesetContainer(RulesetContainer<T> rulesetContainer) => rulesetContainer.SetReplayScore(CreateReplayScore(rulesetContainer.Beatmap));
} }
public abstract class ModAutoplay : Mod, IApplicableFailOverride public abstract class ModAutoplay : Mod, IApplicableFailOverride

View File

@ -2,8 +2,11 @@
// 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 System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Extensions;
using osu.Framework.Extensions.TypeExtensions; using osu.Framework.Extensions.TypeExtensions;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
@ -57,6 +60,11 @@ namespace osu.Game.Rulesets.Scoring
/// </summary> /// </summary>
public readonly BindableInt Combo = new BindableInt(); public readonly BindableInt Combo = new BindableInt();
/// <summary>
/// Create a <see cref="HitWindows"/> for this processor.
/// </summary>
protected virtual HitWindows CreateHitWindows() => new HitWindows();
/// <summary> /// <summary>
/// The current rank. /// The current rank.
/// </summary> /// </summary>
@ -166,7 +174,16 @@ namespace osu.Game.Rulesets.Scoring
score.Accuracy = Math.Round(Accuracy, 4); score.Accuracy = Math.Round(Accuracy, 4);
score.Rank = Rank; score.Rank = Rank;
score.Date = DateTimeOffset.Now; score.Date = DateTimeOffset.Now;
var hitWindows = CreateHitWindows();
foreach (var result in Enum.GetValues(typeof(HitResult)).OfType<HitResult>().Where(r => r > HitResult.None && hitWindows.IsHitResultAllowed(r)))
score.Statistics[result] = GetStatistic(result);
} }
protected abstract int GetStatistic(HitResult result);
public abstract double GetStandardisedScore();
} }
public class ScoreProcessor<TObject> : ScoreProcessor public class ScoreProcessor<TObject> : ScoreProcessor
@ -273,6 +290,8 @@ namespace osu.Game.Rulesets.Scoring
updateScore(); updateScore();
} }
private readonly Dictionary<HitResult, int> scoreResultCounts = new Dictionary<HitResult, int>();
/// <summary> /// <summary>
/// Applies the score change of a <see cref="JudgementResult"/> to this <see cref="ScoreProcessor"/>. /// Applies the score change of a <see cref="JudgementResult"/> to this <see cref="ScoreProcessor"/>.
/// </summary> /// </summary>
@ -284,6 +303,9 @@ namespace osu.Game.Rulesets.Scoring
JudgedHits++; JudgedHits++;
if (result.Type != HitResult.None)
scoreResultCounts[result.Type] = scoreResultCounts.GetOrDefault(result.Type) + 1;
if (result.Judgement.AffectsCombo) if (result.Judgement.AffectsCombo)
{ {
switch (result.Type) switch (result.Type)
@ -340,20 +362,30 @@ namespace osu.Game.Rulesets.Scoring
if (rollingMaxBaseScore != 0) if (rollingMaxBaseScore != 0)
Accuracy.Value = baseScore / rollingMaxBaseScore; Accuracy.Value = baseScore / rollingMaxBaseScore;
switch (Mode.Value) TotalScore.Value = getScore(Mode.Value);
}
private double getScore(ScoringMode mode)
{
switch (mode)
{ {
default:
case ScoringMode.Standardised: case ScoringMode.Standardised:
TotalScore.Value = max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore; return max_score * (base_portion * baseScore / maxBaseScore + combo_portion * HighestCombo / maxHighestCombo) + bonusScore;
break;
case ScoringMode.Classic: case ScoringMode.Classic:
// should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1) // should emulate osu-stable's scoring as closely as we can (https://osu.ppy.sh/help/wiki/Score/ScoreV1)
TotalScore.Value = bonusScore + baseScore * (1 + Math.Max(0, HighestCombo - 1) / 25); return bonusScore + baseScore * (1 + Math.Max(0, HighestCombo - 1) / 25);
break;
} }
} }
protected override int GetStatistic(HitResult result) => scoreResultCounts.GetOrDefault(result);
public override double GetStandardisedScore() => getScore(ScoringMode.Standardised);
protected override void Reset(bool storeResults) protected override void Reset(bool storeResults)
{ {
scoreResultCounts.Clear();
if (storeResults) if (storeResults)
{ {
MaxHits = JudgedHits; MaxHits = JudgedHits;

View File

@ -22,6 +22,7 @@ using osu.Game.Overlays;
using osu.Game.Replays; using osu.Game.Replays;
using osu.Game.Rulesets.Configuration; using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using osu.Game.Scoring;
namespace osu.Game.Rulesets.UI namespace osu.Game.Rulesets.UI
{ {
@ -130,7 +131,7 @@ namespace osu.Game.Rulesets.UI
protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null; protected virtual ReplayInputHandler CreateReplayInputHandler(Replay replay) => null;
public Replay Replay { get; private set; } public Score ReplayScore { get; private set; }
/// <summary> /// <summary>
/// Whether the game is paused. Used to block user input. /// Whether the game is paused. Used to block user input.
@ -140,14 +141,14 @@ namespace osu.Game.Rulesets.UI
/// <summary> /// <summary>
/// Sets a replay to be used, overriding local input. /// Sets a replay to be used, overriding local input.
/// </summary> /// </summary>
/// <param name="replay">The replay, null for local input.</param> /// <param name="replayScore">The replay, null for local input.</param>
public virtual void SetReplay(Replay replay) public virtual void SetReplayScore(Score replayScore)
{ {
if (ReplayInputManager == null) if (ReplayInputManager == null)
throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available"); throw new InvalidOperationException($"A {nameof(KeyBindingInputManager)} which supports replay loading is not available");
Replay = replay; ReplayScore = replayScore;
ReplayInputManager.ReplayInputHandler = replay != null ? CreateReplayInputHandler(replay) : null; ReplayInputManager.ReplayInputHandler = replayScore != null ? CreateReplayInputHandler(replayScore.Replay) : null;
HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null; HasReplayLoaded.Value = ReplayInputManager.ReplayInputHandler != null;
} }
@ -302,9 +303,9 @@ namespace osu.Game.Rulesets.UI
mod.ReadFromConfig(config); mod.ReadFromConfig(config);
} }
public override void SetReplay(Replay replay) public override void SetReplayScore(Score replayScore)
{ {
base.SetReplay(replay); base.SetReplayScore(replayScore);
if (ReplayInputManager?.ReplayInputHandler != null) if (ReplayInputManager?.ReplayInputHandler != null)
ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace; ReplayInputManager.ReplayInputHandler.GamefieldToScreenSpace = Playfield.GamefieldToScreenSpace;

View File

@ -6,6 +6,7 @@ using System.Collections.Generic;
using System.ComponentModel.DataAnnotations.Schema; using System.ComponentModel.DataAnnotations.Schema;
using System.Linq; using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using Newtonsoft.Json.Converters;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Rulesets; using osu.Game.Rulesets;
@ -19,25 +20,39 @@ namespace osu.Game.Scoring
{ {
public int ID { get; set; } public int ID { get; set; }
[JsonProperty("rank")]
[JsonConverter(typeof(StringEnumConverter))]
public ScoreRank Rank { get; set; } public ScoreRank Rank { get; set; }
[JsonProperty("total_score")]
public int TotalScore { get; set; } public int TotalScore { get; set; }
[JsonProperty("accuracy")]
[Column(TypeName="DECIMAL(1,4)")] [Column(TypeName="DECIMAL(1,4)")]
public double Accuracy { get; set; } public double Accuracy { get; set; }
[JsonIgnore]
public double? PP { get; set; } public double? PP { get; set; }
[JsonProperty("max_combo")]
public int MaxCombo { get; set; } public int MaxCombo { get; set; }
public int Combo { get; set; } [JsonIgnore]
public int Combo { get; set; } // Todo: Shouldn't exist in here
[JsonIgnore]
public int RulesetID { get; set; } public int RulesetID { get; set; }
[JsonProperty("passed")]
[NotMapped]
public bool Passed { get; set; } = true;
[JsonIgnore]
public virtual RulesetInfo Ruleset { get; set; } public virtual RulesetInfo Ruleset { get; set; }
private Mod[] mods; private Mod[] mods;
[JsonProperty("mods")]
[NotMapped] [NotMapped]
public Mod[] Mods public Mod[] Mods
{ {
@ -62,6 +77,7 @@ namespace osu.Game.Scoring
private string modsJson; private string modsJson;
[JsonIgnore]
[Column("Mods")] [Column("Mods")]
public string ModsJson public string ModsJson
{ {
@ -84,9 +100,11 @@ namespace osu.Game.Scoring
} }
} }
[JsonIgnore] [NotMapped]
public User User; [JsonProperty("user")]
public User User { get; set; }
[JsonIgnore]
[Column("User")] [Column("User")]
public string UserString public string UserString
{ {
@ -97,15 +115,19 @@ namespace osu.Game.Scoring
[JsonIgnore] [JsonIgnore]
public int BeatmapInfoID { get; set; } public int BeatmapInfoID { get; set; }
[JsonIgnore]
public virtual BeatmapInfo Beatmap { get; set; } public virtual BeatmapInfo Beatmap { get; set; }
[JsonIgnore]
public long? OnlineScoreID { get; set; } public long? OnlineScoreID { get; set; }
[JsonIgnore]
public DateTimeOffset Date { get; set; } public DateTimeOffset Date { get; set; }
[JsonIgnore] [JsonProperty("statistics")]
public Dictionary<HitResult, int> Statistics = new Dictionary<HitResult, int>(); public Dictionary<HitResult, int> Statistics = new Dictionary<HitResult, int>();
[JsonIgnore]
[Column("Statistics")] [Column("Statistics")]
public string StatisticsJson public string StatisticsJson
{ {
@ -125,8 +147,10 @@ namespace osu.Game.Scoring
[JsonIgnore] [JsonIgnore]
public List<ScoreFileInfo> Files { get; set; } public List<ScoreFileInfo> Files { get; set; }
[JsonIgnore]
public string Hash { get; set; } public string Hash { get; set; }
[JsonIgnore]
public bool DeletePending { get; set; } public bool DeletePending { get; set; }
[Serializable] [Serializable]

View File

@ -17,7 +17,9 @@ using osu.Framework.Threading;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Input; using osu.Game.Input;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Online.API;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
using osuTK.Input; using osuTK.Input;
@ -90,7 +92,7 @@ namespace osu.Game.Screens.Menu
buttonArea.Flow.CentreTarget = iconFacade; buttonArea.Flow.CentreTarget = iconFacade;
buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P)); buttonsPlay.Add(new Button(@"solo", @"button-solo-select", FontAwesome.fa_user, new Color4(102, 68, 204, 255), () => OnSolo?.Invoke(), WEDGE_WIDTH, Key.P));
buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), () => OnMulti?.Invoke(), 0, Key.M)); buttonsPlay.Add(new Button(@"multi", @"button-generic-select", FontAwesome.fa_users, new Color4(94, 63, 186, 255), onMulti, 0, Key.M));
buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke())); buttonsPlay.Add(new Button(@"chart", @"button-generic-select", FontAwesome.fa_osu_charts, new Color4(80, 53, 160, 255), () => OnChart?.Invoke()));
buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play); buttonsPlay.ForEach(b => b.VisibleState = ButtonSystemState.Play);
@ -103,19 +105,41 @@ namespace osu.Game.Screens.Menu
buttonArea.AddRange(buttonsTopLevel); buttonArea.AddRange(buttonsTopLevel);
} }
private OsuGame game; [Resolved(CanBeNull = true)]
private OsuGame game { get; set; }
[Resolved]
private APIAccess api { get; set; }
[Resolved(CanBeNull = true)]
private NotificationOverlay notifications { get; set; }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuGame game, IdleTracker idleTracker) private void load(AudioManager audio, IdleTracker idleTracker)
{ {
this.game = game;
isIdle.ValueChanged += updateIdleState; isIdle.ValueChanged += updateIdleState;
if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle); if (idleTracker != null) isIdle.BindTo(idleTracker.IsIdle);
sampleBack = audio.Sample.Get(@"Menu/button-back-select"); sampleBack = audio.Sample.Get(@"Menu/button-back-select");
} }
private void onMulti()
{
if (!api.IsLoggedIn)
{
notifications?.Post(new SimpleNotification
{
Text = "You gotta be logged in to multi 'yo!",
Icon = FontAwesome.fa_globe
});
return;
}
OnMulti?.Invoke();
}
private void updateIdleState(bool isIdle) private void updateIdleState(bool isIdle)
{ {
if (isIdle && State != ButtonSystemState.Exit) if (isIdle && State != ButtonSystemState.Exit)

View File

@ -1,68 +1,89 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Localisation; using osu.Framework.Localisation;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
namespace osu.Game.Screens.Multi.Components namespace osu.Game.Screens.Multi.Components
{ {
public class BeatmapTitle : FillFlowContainer<OsuSpriteText> public class BeatmapTitle : CompositeDrawable
{ {
private readonly OsuSpriteText beatmapTitle, beatmapDash, beatmapArtist; public readonly IBindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
public float TextSize private readonly LinkFlowContainer textFlow;
{
set { beatmapTitle.TextSize = beatmapDash.TextSize = beatmapArtist.TextSize = value; }
}
private BeatmapInfo beatmap;
public BeatmapInfo Beatmap
{
set
{
if (value == beatmap) return;
beatmap = value;
if (IsLoaded)
updateText();
}
}
public BeatmapTitle() public BeatmapTitle()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
Children = new[] InternalChild = textFlow = new LinkFlowContainer { AutoSizeAxes = Axes.Both };
{
beatmapTitle = new OsuSpriteText { Font = @"Exo2.0-BoldItalic", },
beatmapDash = new OsuSpriteText { Font = @"Exo2.0-BoldItalic", },
beatmapArtist = new OsuSpriteText { Font = @"Exo2.0-RegularItalic", },
};
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
updateText(); Beatmap.BindValueChanged(v => updateText(), true);
} }
private float textSize = OsuSpriteText.FONT_SIZE;
public float TextSize
{
get => textSize;
set
{
if (textSize == value)
return;
textSize = value;
updateText();
}
}
[Resolved]
private OsuColour colours { get; set; }
private void updateText() private void updateText()
{ {
if (beatmap == null) if (!IsLoaded)
{ return;
beatmapTitle.Text = "Changing map";
beatmapDash.Text = beatmapArtist.Text = string.Empty; textFlow.Clear();
}
if (Beatmap.Value == null)
textFlow.AddText("No beatmap selected", s =>
{
s.TextSize = TextSize;
s.Colour = colours.PinkLight;
});
else else
{ {
beatmapTitle.Text = new LocalisedString((beatmap.Metadata.TitleUnicode, beatmap.Metadata.Title)); textFlow.AddLink(new[]
beatmapDash.Text = @" - "; {
beatmapArtist.Text = new LocalisedString((beatmap.Metadata.ArtistUnicode, beatmap.Metadata.Artist)); new OsuSpriteText
{
Text = new LocalisedString((Beatmap.Value.Metadata.ArtistUnicode, Beatmap.Value.Metadata.Artist)),
TextSize = TextSize,
},
new OsuSpriteText
{
Text = " - ",
TextSize = TextSize,
},
new OsuSpriteText
{
Text = new LocalisedString((Beatmap.Value.Metadata.TitleUnicode, Beatmap.Value.Metadata.Title)),
TextSize = TextSize,
}
}, null, LinkAction.OpenBeatmap, Beatmap.Value.OnlineBeatmapID.ToString(), "Open beatmap");
} }
} }
} }

View File

@ -1,70 +1,77 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Containers;
using osu.Game.Online.Chat;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osuTK; using osuTK;
namespace osu.Game.Screens.Multi.Components namespace osu.Game.Screens.Multi.Components
{ {
public class BeatmapTypeInfo : FillFlowContainer public class BeatmapTypeInfo : CompositeDrawable
{ {
private readonly ModeTypeInfo modeTypeInfo; public readonly IBindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
private readonly BeatmapTitle beatmapTitle; public readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
private readonly OsuSpriteText beatmapAuthor; public readonly IBindable<GameType> Type = new Bindable<GameType>();
public BeatmapInfo Beatmap
{
set
{
modeTypeInfo.Beatmap = beatmapTitle.Beatmap = value;
beatmapAuthor.Text = value == null ? string.Empty : $"mapped by {value.Metadata.Author}";
}
}
public GameType Type
{
set { modeTypeInfo.Type = value; }
}
public BeatmapTypeInfo() public BeatmapTypeInfo()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
LayoutDuration = 100;
Spacing = new Vector2(5f, 0f);
Children = new Drawable[] BeatmapTitle beatmapTitle;
ModeTypeInfo modeTypeInfo;
LinkFlowContainer beatmapAuthor;
InternalChild = new FillFlowContainer
{ {
modeTypeInfo = new ModeTypeInfo(), AutoSizeAxes = Axes.Both,
new Container Direction = FillDirection.Horizontal,
LayoutDuration = 100,
Spacing = new Vector2(5, 0),
Children = new Drawable[]
{ {
AutoSizeAxes = Axes.X, modeTypeInfo = new ModeTypeInfo(),
Height = 30, new Container
Margin = new MarginPadding { Left = 5 },
Children = new Drawable[]
{ {
beatmapTitle = new BeatmapTitle(), AutoSizeAxes = Axes.X,
beatmapAuthor = new OsuSpriteText Height = 30,
Margin = new MarginPadding { Left = 5 },
Children = new Drawable[]
{ {
Anchor = Anchor.BottomLeft, beatmapTitle = new BeatmapTitle(),
Origin = Anchor.BottomLeft, beatmapAuthor = new LinkFlowContainer(s => s.TextSize = 14)
TextSize = 14, {
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
AutoSizeAxes = Axes.Both
},
}, },
}, },
}, }
}; };
}
[BackgroundDependencyLoader] modeTypeInfo.Beatmap.BindTo(Beatmap);
private void load(OsuColour colours) modeTypeInfo.Ruleset.BindTo(Ruleset);
{ modeTypeInfo.Type.BindTo(Type);
beatmapAuthor.Colour = colours.Gray9;
beatmapTitle.Beatmap.BindTo(Beatmap);
Beatmap.BindValueChanged(v =>
{
beatmapAuthor.Clear();
if (v != null)
{
beatmapAuthor.AddText("mapped by ", s => s.Colour = OsuColour.Gray(0.8f));
beatmapAuthor.AddLink(v.Metadata.Author.Username, null, LinkAction.OpenUserProfile, v.Metadata.Author.Id.ToString(), "View Profile");
}
});
} }
} }
} }

View File

@ -0,0 +1,38 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events;
namespace osu.Game.Screens.Multi.Components
{
public abstract class DisableableTabControl<T> : TabControl<T>
{
public readonly BindableBool Enabled = new BindableBool();
protected override void AddTabItem(TabItem<T> tab, bool addToDropdown = true)
{
if (tab is DisableableTabItem<T> disableable)
disableable.Enabled.BindTo(Enabled);
base.AddTabItem(tab, addToDropdown);
}
protected abstract class DisableableTabItem<T> : TabItem<T>
{
public readonly BindableBool Enabled = new BindableBool();
protected DisableableTabItem(T value)
: base(value)
{
}
protected override bool OnClick(ClickEvent e)
{
if (!Enabled)
return true;
return base.OnClick(e);
}
}
}
}

View File

@ -1,83 +1,67 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Rulesets;
using osuTK; using osuTK;
namespace osu.Game.Screens.Multi.Components namespace osu.Game.Screens.Multi.Components
{ {
public class ModeTypeInfo : Container public class ModeTypeInfo : CompositeDrawable
{ {
private const float height = 30; private const float height = 30;
private const float transition_duration = 100; private const float transition_duration = 100;
private readonly Container rulesetContainer, gameTypeContainer; private readonly Container rulesetContainer;
public BeatmapInfo Beatmap public readonly IBindable<BeatmapInfo> Beatmap = new Bindable<BeatmapInfo>();
{ public readonly IBindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
set public readonly IBindable<GameType> Type = new Bindable<GameType>();
{
if (value != null)
{
rulesetContainer.FadeIn(transition_duration);
rulesetContainer.Children = new[]
{
new DifficultyIcon(value)
{
Size = new Vector2(height),
},
};
}
else
{
rulesetContainer.FadeOut(transition_duration);
}
}
}
public GameType Type
{
set
{
gameTypeContainer.Children = new[]
{
new DrawableGameType(value)
{
Size = new Vector2(height),
},
};
}
}
public ModeTypeInfo() public ModeTypeInfo()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Children = new[] Container gameTypeContainer;
InternalChild = new FillFlowContainer
{ {
new FillFlowContainer AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
LayoutDuration = 100,
Children = new[]
{ {
AutoSizeAxes = Axes.Both, rulesetContainer = new Container
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
LayoutDuration = 100,
Children = new[]
{ {
rulesetContainer = new Container AutoSizeAxes = Axes.Both,
{ },
AutoSizeAxes = Axes.Both, gameTypeContainer = new Container
}, {
gameTypeContainer = new Container AutoSizeAxes = Axes.Both,
{
AutoSizeAxes = Axes.Both,
},
}, },
}, },
}; };
Beatmap.BindValueChanged(updateBeatmap);
Ruleset.BindValueChanged(_ => updateBeatmap(Beatmap.Value));
Type.BindValueChanged(v => gameTypeContainer.Child = new DrawableGameType(v) { Size = new Vector2(height) });
}
private void updateBeatmap(BeatmapInfo beatmap)
{
if (beatmap != null)
{
rulesetContainer.FadeIn(transition_duration);
rulesetContainer.Child = new DifficultyIcon(beatmap, Ruleset.Value) { Size = new Vector2(height) };
}
else
rulesetContainer.FadeOut(transition_duration);
} }
} }
} }

View File

@ -1,69 +1,67 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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.Collections.Generic;
using System.Linq;
using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Users;
namespace osu.Game.Screens.Multi.Components namespace osu.Game.Screens.Multi.Components
{ {
public class ParticipantCount : FillFlowContainer public class ParticipantCountDisplay : CompositeDrawable
{ {
private const float text_size = 30; private const float text_size = 30;
private const float transition_duration = 100; private const float transition_duration = 100;
private readonly OsuSpriteText count, slash, maxText; private readonly OsuSpriteText slash, maxText;
public int Count public readonly IBindable<IEnumerable<User>> Participants = new Bindable<IEnumerable<User>>();
{ public readonly IBindable<int> ParticipantCount = new Bindable<int>();
set => count.Text = value.ToString(); public readonly IBindable<int?> MaxParticipants = new Bindable<int?>();
}
private int? max; public ParticipantCountDisplay()
public int? Max
{
get => max;
set
{
if (value == max) return;
max = value;
updateMax();
}
}
public ParticipantCount()
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Direction = FillDirection.Horizontal;
LayoutDuration = transition_duration;
Children = new[] OsuSpriteText count;
InternalChild = new FillFlowContainer
{ {
count = new OsuSpriteText AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
LayoutDuration = transition_duration,
Children = new[]
{ {
TextSize = text_size, count = new OsuSpriteText
Font = @"Exo2.0-Bold" {
}, TextSize = text_size,
slash = new OsuSpriteText Font = @"Exo2.0-Bold"
{ },
Text = @"/", slash = new OsuSpriteText
TextSize = text_size, {
Font = @"Exo2.0-Light" Text = @"/",
}, TextSize = text_size,
maxText = new OsuSpriteText Font = @"Exo2.0-Light"
{ },
TextSize = text_size, maxText = new OsuSpriteText
Font = @"Exo2.0-Light" {
}, TextSize = text_size,
Font = @"Exo2.0-Light"
},
}
}; };
updateMax(); Participants.BindValueChanged(v => count.Text = v.Count().ToString());
MaxParticipants.BindValueChanged(_ => updateMax(), true);
ParticipantCount.BindValueChanged(v => count.Text = v.ToString("#,0"));
} }
private void updateMax() private void updateMax()
{ {
if (Max == null) if (MaxParticipants.Value == null)
{ {
slash.FadeOut(transition_duration); slash.FadeOut(transition_duration);
maxText.FadeOut(transition_duration); maxText.FadeOut(transition_duration);
@ -71,7 +69,7 @@ namespace osu.Game.Screens.Multi.Components
else else
{ {
slash.FadeIn(transition_duration); slash.FadeIn(transition_duration);
maxText.Text = Max.ToString(); maxText.Text = MaxParticipants.Value.ToString();
maxText.FadeIn(transition_duration); maxText.FadeIn(transition_duration);
} }
} }

View File

@ -1,145 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.Multi.Components
{
public class ParticipantInfo : Container
{
private readonly Container flagContainer;
private readonly OsuSpriteText host;
private readonly FillFlowContainer levelRangeContainer;
private readonly OsuSpriteText levelRangeLower;
private readonly OsuSpriteText levelRangeHigher;
public User Host
{
set
{
host.Text = value.Username;
flagContainer.Children = new[] { new DrawableFlag(value.Country) { RelativeSizeAxes = Axes.Both } };
}
}
public IEnumerable<User> Participants
{
set
{
var ranks = value.Select(u => u.Statistics.Ranks.Global);
levelRangeLower.Text = ranks.Min().ToString();
levelRangeHigher.Text = ranks.Max().ToString();
}
}
public ParticipantInfo(string rankPrefix = null)
{
RelativeSizeAxes = Axes.X;
Height = 15f;
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
flagContainer = new Container
{
Width = 22f,
RelativeSizeAxes = Axes.Y,
},
new Container //todo: team banners
{
Width = 38f,
RelativeSizeAxes = Axes.Y,
CornerRadius = 2f,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"ad387e"),
},
},
},
new OsuSpriteText
{
Text = "hosted by",
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = 14,
},
host = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = 14,
Font = @"Exo2.0-BoldItalic",
},
},
},
levelRangeContainer = new FillFlowContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
new OsuSpriteText
{
Text = rankPrefix,
TextSize = 14,
},
new OsuSpriteText
{
Text = "#",
TextSize = 14,
},
levelRangeLower = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-Bold",
},
new OsuSpriteText
{
Text = " - ",
TextSize = 14,
},
new OsuSpriteText
{
Text = "#",
TextSize = 14,
},
levelRangeHigher = new OsuSpriteText
{
TextSize = 14,
Font = @"Exo2.0-Bold",
},
},
},
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
levelRangeContainer.Colour = colours.Gray9;
host.Colour = colours.Blue;
}
}
}

View File

@ -0,0 +1,111 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.RoomStatuses;
namespace osu.Game.Screens.Multi.Components
{
public class RoomStatusInfo : CompositeDrawable
{
private readonly RoomBindings bindings = new RoomBindings();
public RoomStatusInfo(Room room)
{
bindings.Room = room;
AutoSizeAxes = Axes.Both;
StatusPart statusPart;
EndDatePart endDatePart;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
statusPart = new StatusPart
{
TextSize = 14,
Font = "Exo2.0-Bold"
},
endDatePart = new EndDatePart { TextSize = 14 }
}
};
statusPart.EndDate.BindTo(bindings.EndDate);
statusPart.Status.BindTo(bindings.Status);
statusPart.Availability.BindTo(bindings.Availability);
endDatePart.EndDate.BindTo(bindings.EndDate);
}
private class EndDatePart : DrawableDate
{
public readonly IBindable<DateTimeOffset> EndDate = new Bindable<DateTimeOffset>();
public EndDatePart()
: base(DateTimeOffset.UtcNow)
{
EndDate.BindValueChanged(d => Date = d);
}
protected override string Format()
{
var diffToNow = Date.Subtract(DateTimeOffset.Now);
if (diffToNow.TotalSeconds < -5)
return $"Closed {base.Format()}";
if (diffToNow.TotalSeconds < 0)
return "Closed";
if (diffToNow.TotalSeconds < 5)
return "Closing soon";
return $"Closing {base.Format()}";
}
}
private class StatusPart : EndDatePart
{
public readonly IBindable<RoomStatus> Status = new Bindable<RoomStatus>();
public readonly IBindable<RoomAvailability> Availability = new Bindable<RoomAvailability>();
[Resolved]
private OsuColour colours { get; set; }
public StatusPart()
{
EndDate.BindValueChanged(_ => Format());
Status.BindValueChanged(_ => Format());
Availability.BindValueChanged(_ => Format());
}
protected override void LoadComplete()
{
base.LoadComplete();
Text = Format();
}
protected override string Format()
{
if (!IsLoaded)
return string.Empty;
RoomStatus status = Date < DateTimeOffset.Now ? new RoomStatusEnded() : Status.Value ?? new RoomStatusOpen();
this.FadeColour(status.GetAppropriateColour(colours), 100);
return $"{Availability.Value.GetDescription()}, {status.Message}";
}
}
}
}

View File

@ -10,7 +10,6 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.SearchableList; using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Screens;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
@ -86,7 +85,12 @@ namespace osu.Game.Screens.Multi
}, },
}; };
breadcrumbs.Current.ValueChanged += s => screenType.Text = ((MultiplayerScreen)s).Type.ToLowerInvariant(); breadcrumbs.Current.ValueChanged += s =>
{
if (s is IMultiplayerSubScreen mpScreen)
screenType.Text = mpScreen.ShortTitle.ToLowerInvariant();
};
breadcrumbs.Current.TriggerChange(); breadcrumbs.Current.TriggerChange();
} }
@ -99,7 +103,8 @@ namespace osu.Game.Screens.Multi
private class HeaderBreadcrumbControl : ScreenBreadcrumbControl private class HeaderBreadcrumbControl : ScreenBreadcrumbControl
{ {
public HeaderBreadcrumbControl(Screen initialScreen) : base(initialScreen) public HeaderBreadcrumbControl(Screen initialScreen)
: base(initialScreen)
{ {
} }

View File

@ -0,0 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Screens.Multi
{
public interface IMultiplayerSubScreen
{
string ShortTitle { get; }
}
}

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Configuration;
using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Lounge.Components;
namespace osu.Game.Screens.Multi
{
public interface IRoomManager
{
/// <summary>
/// Invoked when the <see cref="Room"/>s have been updated.
/// </summary>
event Action RoomsUpdated;
/// <summary>
/// All the active <see cref="Room"/>s.
/// </summary>
IBindableCollection<Room> Rooms { get; }
/// <summary>
/// Creates a new <see cref="Room"/>.
/// </summary>
/// <param name="room">The <see cref="Room"/> to create.</param>
/// <param name="onSuccess">An action to be invoked if the creation succeeds.</param>
/// <param name="onError">An action to be invoked if an error occurred.</param>
void CreateRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null);
/// <summary>
/// Joins a <see cref="Room"/>.
/// </summary>
/// <param name="room">The <see cref="Room"/> to join. <see cref="Room.RoomID"/> must be populated.</param>
/// <param name="onSuccess"></param>
/// <param name="onError"></param>
void JoinRoom(Room room, Action<Room> onSuccess = null, Action<string> onError = null);
/// <summary>
/// Parts the currently-joined <see cref="Room"/>.
/// </summary>
void PartRoom();
/// <summary>
/// Queries for <see cref="Room"/>s matching a new <see cref="FilterCriteria"/>.
/// </summary>
/// <param name="criteria">The <see cref="FilterCriteria"/> to match.</param>
void Filter(FilterCriteria criteria);
}
}

View File

@ -5,12 +5,10 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions; 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.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Drawables; using osu.Game.Beatmaps.Drawables;
using osu.Game.Graphics; using osu.Game.Graphics;
@ -18,11 +16,11 @@ using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Users; using osu.Game.Screens.Multi.Components;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Screens.Multi.Components namespace osu.Game.Screens.Multi.Lounge.Components
{ {
public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable public class DrawableRoom : OsuClickableContainer, IStateful<SelectionState>, IFilterable
{ {
@ -30,18 +28,21 @@ namespace osu.Game.Screens.Multi.Components
private const float corner_radius = 5; private const float corner_radius = 5;
private const float transition_duration = 60; private const float transition_duration = 60;
private const float content_padding = 10; private const float content_padding = 10;
private const float height = 100; private const float height = 110;
private const float side_strip_width = 5; private const float side_strip_width = 5;
private const float cover_width = 145; private const float cover_width = 145;
private readonly Box selectionBox; public event Action<SelectionState> StateChanged;
private readonly Bindable<string> nameBind = new Bindable<string>(); private readonly RoomBindings bindings = new RoomBindings();
private readonly Bindable<User> hostBind = new Bindable<User>();
private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>(); private readonly Box selectionBox;
private readonly Bindable<GameType> typeBind = new Bindable<GameType>(); private UpdateableBeatmapBackgroundSprite background;
private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>(); private BeatmapTitle beatmapTitle;
private readonly Bindable<IEnumerable<User>> participantsBind = new Bindable<IEnumerable<User>>(); private ModeTypeInfo modeTypeInfo;
[Resolved]
private BeatmapManager beatmaps { get; set; }
public readonly Room Room; public readonly Room Room;
@ -76,22 +77,10 @@ namespace osu.Game.Screens.Multi.Components
} }
} }
private Action<DrawableRoom> action;
public new Action<DrawableRoom> Action
{
get { return action; }
set
{
action = value;
Enabled.Value = action != null;
}
}
public event Action<SelectionState> StateChanged;
public DrawableRoom(Room room) public DrawableRoom(Room room)
{ {
Room = room; Room = room;
bindings.Room = room;
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
Height = height + SELECTION_BORDER_WIDTH * 2; Height = height + SELECTION_BORDER_WIDTH * 2;
@ -110,11 +99,8 @@ namespace osu.Game.Screens.Multi.Components
private void load(OsuColour colours) private void load(OsuColour colours)
{ {
Box sideStrip; Box sideStrip;
UpdateableBeatmapSetCover cover;
OsuSpriteText name, status;
ParticipantInfo participantInfo; ParticipantInfo participantInfo;
BeatmapTitle beatmapTitle; OsuSpriteText name;
ModeTypeInfo modeTypeInfo;
Children = new Drawable[] Children = new Drawable[]
{ {
@ -146,12 +132,13 @@ namespace osu.Game.Screens.Multi.Components
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = side_strip_width, Width = side_strip_width,
}, },
cover = new UpdateableBeatmapSetCover new Container
{ {
Width = cover_width,
RelativeSizeAxes = Axes.Y, RelativeSizeAxes = Axes.Y,
Width = cover_width,
Masking = true, Masking = true,
Margin = new MarginPadding { Left = side_strip_width }, Margin = new MarginPadding { Left = side_strip_width },
Child = background = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both }
}, },
new Container new Container
{ {
@ -172,10 +159,7 @@ namespace osu.Game.Screens.Multi.Components
Spacing = new Vector2(5f), Spacing = new Vector2(5f),
Children = new Drawable[] Children = new Drawable[]
{ {
name = new OsuSpriteText name = new OsuSpriteText { TextSize = 18 },
{
TextSize = 18,
},
participantInfo = new ParticipantInfo(), participantInfo = new ParticipantInfo(),
}, },
}, },
@ -186,18 +170,11 @@ namespace osu.Game.Screens.Multi.Components
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical, Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 5),
Children = new Drawable[] Children = new Drawable[]
{ {
status = new OsuSpriteText new RoomStatusInfo(Room),
{ beatmapTitle = new BeatmapTitle { TextSize = 14 },
TextSize = 14,
Font = @"Exo2.0-Bold",
},
beatmapTitle = new BeatmapTitle
{
TextSize = 14,
Colour = colours.Gray9
},
}, },
}, },
modeTypeInfo = new ModeTypeInfo modeTypeInfo = new ModeTypeInfo
@ -212,32 +189,21 @@ namespace osu.Game.Screens.Multi.Components
}, },
}; };
nameBind.ValueChanged += n => name.Text = n; background.Beatmap.BindTo(bindings.CurrentBeatmap);
hostBind.ValueChanged += h => participantInfo.Host = h; modeTypeInfo.Beatmap.BindTo(bindings.CurrentBeatmap);
typeBind.ValueChanged += m => modeTypeInfo.Type = m; modeTypeInfo.Ruleset.BindTo(bindings.CurrentRuleset);
participantsBind.ValueChanged += p => participantInfo.Participants = p; modeTypeInfo.Type.BindTo(bindings.Type);
beatmapTitle.Beatmap.BindTo(bindings.CurrentBeatmap);
participantInfo.Host.BindTo(bindings.Host);
participantInfo.Participants.BindTo(bindings.Participants);
participantInfo.ParticipantCount.BindTo(bindings.ParticipantCount);
statusBind.ValueChanged += s => bindings.Name.BindValueChanged(n => name.Text = n, true);
bindings.Status.BindValueChanged(s =>
{ {
status.Text = s.Message; foreach (Drawable d in new Drawable[] { selectionBox, sideStrip })
foreach (Drawable d in new Drawable[] { selectionBox, sideStrip, status })
d.FadeColour(s.GetAppropriateColour(colours), transition_duration); d.FadeColour(s.GetAppropriateColour(colours), transition_duration);
}; }, true);
beatmapBind.ValueChanged += b =>
{
cover.BeatmapSet = b?.BeatmapSet;
beatmapTitle.Beatmap = b;
modeTypeInfo.Beatmap = b;
};
nameBind.BindTo(Room.Name);
hostBind.BindTo(Room.Host);
statusBind.BindTo(Room.Status);
typeBind.BindTo(Room.Type);
beatmapBind.BindTo(Room.Beatmap);
participantsBind.BindTo(Room.Participants);
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -245,16 +211,5 @@ namespace osu.Game.Screens.Multi.Components
base.LoadComplete(); base.LoadComplete();
this.FadeInFromZero(transition_duration); this.FadeInFromZero(transition_duration);
} }
protected override bool OnClick(ClickEvent e)
{
if (Enabled.Value)
{
Action?.Invoke(this);
State = SelectionState.Selected;
}
return true;
}
} }
} }

View File

@ -0,0 +1,43 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
using osu.Game.Graphics;
using osu.Game.Overlays.SearchableList;
using osuTK.Graphics;
namespace osu.Game.Screens.Multi.Lounge.Components
{
public class FilterControl : SearchableListFilterControl<PrimaryFilter, SecondaryFilter>
{
protected override Color4 BackgroundColour => OsuColour.FromHex(@"362e42");
protected override PrimaryFilter DefaultTab => PrimaryFilter.Open;
public FilterControl()
{
DisplayStyleControl.Hide();
}
public FilterCriteria CreateCriteria() => new FilterCriteria
{
SearchString = Search.Current.Value ?? string.Empty,
PrimaryFilter = Tabs.Current,
SecondaryFilter = DisplayStyleControl.Dropdown.Current
};
}
public enum PrimaryFilter
{
Open,
[Description("Recently Ended")]
RecentlyEnded,
Participated,
Owned,
}
public enum SecondaryFilter
{
Public,
//Private,
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Screens.Multi.Lounge.Components
{
public class FilterCriteria
{
public string SearchString;
public PrimaryFilter PrimaryFilter;
public SecondaryFilter SecondaryFilter;
}
}

View File

@ -0,0 +1,118 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using Humanizer;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.Multi.Lounge.Components
{
public class ParticipantInfo : Container
{
private readonly FillFlowContainer summaryContainer;
public readonly IBindable<User> Host = new Bindable<User>();
public readonly IBindable<IEnumerable<User>> Participants = new Bindable<IEnumerable<User>>();
public readonly IBindable<int> ParticipantCount = new Bindable<int>();
public ParticipantInfo()
{
OsuSpriteText summary;
RelativeSizeAxes = Axes.X;
Height = 15f;
OsuSpriteText levelRangeHigher;
OsuSpriteText levelRangeLower;
Container flagContainer;
LinkFlowContainer hostText;
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5f, 0f),
Children = new Drawable[]
{
flagContainer = new Container
{
Width = 22f,
RelativeSizeAxes = Axes.Y,
},
/*new Container //todo: team banners
{
Width = 38f,
RelativeSizeAxes = Axes.Y,
CornerRadius = 2f,
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"ad387e"),
},
},
},*/
hostText = new LinkFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both
}
},
},
summaryContainer = new FillFlowContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Children = new[]
{
summary = new OsuSpriteText
{
Text = "0 participants",
TextSize = 14,
}
},
},
};
Host.BindValueChanged(v =>
{
hostText.Clear();
hostText.AddText("hosted by ");
hostText.AddLink(v.Username, null, LinkAction.OpenUserProfile, v.Id.ToString(), "Open profile", s => s.Font = "Exo2.0-BoldItalic");
flagContainer.Child = new DrawableFlag(v.Country) { RelativeSizeAxes = Axes.Both };
});
ParticipantCount.BindValueChanged(v => summary.Text = $"{v:#,0}{" participant".Pluralize(v == 1)}");
/*Participants.BindValueChanged(v =>
{
var ranks = v.Select(u => u.Statistics.Ranks.Global);
levelRangeLower.Text = ranks.Min().ToString();
levelRangeHigher.Text = ranks.Max().ToString();
});*/
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
summaryContainer.Colour = colours.Gray9;
}
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 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.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
@ -17,66 +16,35 @@ using osu.Game.Graphics;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Screens.Multi.Components;
using osu.Game.Users; using osu.Game.Users;
using osuTK; using osuTK;
using osuTK.Graphics; using osuTK.Graphics;
namespace osu.Game.Screens.Multi.Components namespace osu.Game.Screens.Multi.Lounge.Components
{ {
public class RoomInspector : Container public class RoomInspector : Container
{ {
private const float transition_duration = 100; private const float transition_duration = 100;
public readonly IBindable<Room> Room = new Bindable<Room>();
private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 }; private readonly MarginPadding contentPadding = new MarginPadding { Horizontal = 20, Vertical = 10 };
private readonly Bindable<string> nameBind = new Bindable<string>();
private readonly Bindable<User> hostBind = new Bindable<User>(); private readonly RoomBindings bindings = new RoomBindings();
private readonly Bindable<RoomStatus> statusBind = new Bindable<RoomStatus>();
private readonly Bindable<GameType> typeBind = new Bindable<GameType>();
private readonly Bindable<BeatmapInfo> beatmapBind = new Bindable<BeatmapInfo>();
private readonly Bindable<int?> maxParticipantsBind = new Bindable<int?>();
private readonly Bindable<IEnumerable<User>> participantsBind = new Bindable<IEnumerable<User>>();
private OsuColour colours; private OsuColour colours;
private Box statusStrip; private Box statusStrip;
private UpdateableBeatmapSetCover cover; private UpdateableBeatmapBackgroundSprite background;
private ParticipantCount participantCount; private ParticipantCountDisplay participantCount;
private FillFlowContainer topFlow, participantsFlow; private FillFlowContainer topFlow, participantsFlow;
private OsuSpriteText name, status; private OsuSpriteText name, status;
private BeatmapTypeInfo beatmapTypeInfo; private BeatmapTypeInfo beatmapTypeInfo;
private ScrollContainer participantsScroll; private ScrollContainer participantsScroll;
private ParticipantInfo participantInfo; private ParticipantInfo participantInfo;
private Room room; [Resolved]
public Room Room private BeatmapManager beatmaps { get; set; }
{
get { return room; }
set
{
if (value == room) return;
room = value;
nameBind.UnbindBindings();
hostBind.UnbindBindings();
statusBind.UnbindBindings();
typeBind.UnbindBindings();
beatmapBind.UnbindBindings();
maxParticipantsBind.UnbindBindings();
participantsBind.UnbindBindings();
if (room != null)
{
nameBind.BindTo(room.Name);
hostBind.BindTo(room.Host);
statusBind.BindTo(room.Status);
typeBind.BindTo(room.Type);
beatmapBind.BindTo(room.Beatmap);
maxParticipantsBind.BindTo(room.MaxParticipants);
participantsBind.BindTo(room.Participants);
}
updateState();
}
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(OsuColour colours) private void load(OsuColour colours)
@ -104,10 +72,7 @@ namespace osu.Game.Screens.Multi.Components
Masking = true, Masking = true,
Children = new Drawable[] Children = new Drawable[]
{ {
cover = new UpdateableBeatmapSetCover background = new UpdateableBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both },
{
RelativeSizeAxes = Axes.Both,
},
new Box new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
@ -119,7 +84,7 @@ namespace osu.Game.Screens.Multi.Components
Padding = new MarginPadding(20), Padding = new MarginPadding(20),
Children = new Drawable[] Children = new Drawable[]
{ {
participantCount = new ParticipantCount participantCount = new ParticipantCountDisplay
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
@ -177,7 +142,7 @@ namespace osu.Game.Screens.Multi.Components
Padding = contentPadding, Padding = contentPadding,
Children = new Drawable[] Children = new Drawable[]
{ {
participantInfo = new ParticipantInfo(@"Rank Range "), participantInfo = new ParticipantInfo(),
}, },
}, },
}, },
@ -201,26 +166,48 @@ namespace osu.Game.Screens.Multi.Components
}, },
}; };
nameBind.ValueChanged += n => name.Text = n; participantInfo.Host.BindTo(bindings.Host);
hostBind.ValueChanged += h => participantInfo.Host = h; participantInfo.ParticipantCount.BindTo(bindings.ParticipantCount);
typeBind.ValueChanged += t => beatmapTypeInfo.Type = t; participantInfo.Participants.BindTo(bindings.Participants);
maxParticipantsBind.ValueChanged += m => participantCount.Max = m;
statusBind.ValueChanged += displayStatus;
beatmapBind.ValueChanged += b => participantCount.Participants.BindTo(bindings.Participants);
participantCount.ParticipantCount.BindTo(bindings.ParticipantCount);
participantCount.MaxParticipants.BindTo(bindings.MaxParticipants);
beatmapTypeInfo.Beatmap.BindTo(bindings.CurrentBeatmap);
beatmapTypeInfo.Ruleset.BindTo(bindings.CurrentRuleset);
beatmapTypeInfo.Type.BindTo(bindings.Type);
background.Beatmap.BindTo(bindings.CurrentBeatmap);
bindings.Status.BindValueChanged(displayStatus);
bindings.Participants.BindValueChanged(p => participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u)));
bindings.Name.BindValueChanged(n => name.Text = n);
Room.BindValueChanged(updateRoom, true);
}
private void updateRoom(Room room)
{
bindings.Room = room;
if (room != null)
{ {
cover.BeatmapSet = b?.BeatmapSet; participantsFlow.FadeIn(transition_duration);
beatmapTypeInfo.Beatmap = b; participantCount.FadeIn(transition_duration);
}; beatmapTypeInfo.FadeIn(transition_duration);
name.FadeIn(transition_duration);
participantsBind.ValueChanged += p => participantInfo.FadeIn(transition_duration);
}
else
{ {
participantCount.Count = p.Count(); participantsFlow.FadeOut(transition_duration);
participantInfo.Participants = p; participantCount.FadeOut(transition_duration);
participantsFlow.ChildrenEnumerable = p.Select(u => new UserTile(u)); beatmapTypeInfo.FadeOut(transition_duration);
}; name.FadeOut(transition_duration);
participantInfo.FadeOut(transition_duration);
updateState(); displayStatus(new RoomStatusNoneSelected());
}
} }
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()
@ -239,32 +226,6 @@ namespace osu.Game.Screens.Multi.Components
status.FadeColour(c, transition_duration); status.FadeColour(c, transition_duration);
} }
private void updateState()
{
if (Room == null)
{
cover.BeatmapSet = null;
participantsFlow.FadeOut(transition_duration);
participantCount.FadeOut(transition_duration);
beatmapTypeInfo.FadeOut(transition_duration);
name.FadeOut(transition_duration);
participantInfo.FadeOut(transition_duration);
displayStatus(new RoomStatusNoneSelected());
}
else
{
participantsFlow.FadeIn(transition_duration);
participantCount.FadeIn(transition_duration);
beatmapTypeInfo.FadeIn(transition_duration);
name.FadeIn(transition_duration);
participantInfo.FadeIn(transition_duration);
statusBind.TriggerChange();
beatmapBind.TriggerChange();
}
}
private class UserTile : Container, IHasTooltip private class UserTile : Container, IHasTooltip
{ {
private readonly User user; private readonly User user;

View File

@ -0,0 +1,135 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osuTK;
namespace osu.Game.Screens.Multi.Lounge.Components
{
public class RoomsContainer : CompositeDrawable
{
public Action<Room> JoinRequested;
private readonly Bindable<Room> selectedRoom = new Bindable<Room>();
public IBindable<Room> SelectedRoom => selectedRoom;
private readonly IBindableCollection<Room> rooms = new BindableCollection<Room>();
private readonly FillFlowContainer<DrawableRoom> roomFlow;
public IReadOnlyList<DrawableRoom> Rooms => roomFlow;
[Resolved]
private IRoomManager roomManager { get; set; }
public RoomsContainer()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
InternalChild = roomFlow = new FillFlowContainer<DrawableRoom>
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
Spacing = new Vector2(2),
};
}
[BackgroundDependencyLoader]
private void load()
{
rooms.BindTo(roomManager.Rooms);
rooms.ItemsAdded += addRooms;
rooms.ItemsRemoved += removeRooms;
roomManager.RoomsUpdated += updateSorting;
addRooms(rooms);
}
private FilterCriteria currentFilter;
public void Filter(FilterCriteria criteria)
{
roomFlow.Children.ForEach(r =>
{
if (criteria == null)
r.MatchingFilter = true;
else
{
bool matchingFilter = true;
matchingFilter &= r.FilterTerms.Any(term => term.IndexOf(criteria.SearchString, StringComparison.InvariantCultureIgnoreCase) >= 0);
switch (criteria.SecondaryFilter)
{
default:
case SecondaryFilter.Public:
r.MatchingFilter = r.Room.Availability.Value == RoomAvailability.Public;
break;
}
r.MatchingFilter = matchingFilter;
}
});
currentFilter = criteria;
}
private void addRooms(IEnumerable<Room> rooms)
{
foreach (var r in rooms)
roomFlow.Add(new DrawableRoom(r) { Action = () => selectRoom(r) });
Filter(currentFilter);
}
private void removeRooms(IEnumerable<Room> rooms)
{
foreach (var r in rooms)
{
var toRemove = roomFlow.Single(d => d.Room == r);
toRemove.Action = null;
roomFlow.Remove(toRemove);
selectRoom(null);
}
}
private void updateSorting()
{
foreach (var room in roomFlow)
roomFlow.SetLayoutPosition(room, room.Room.Position);
}
private void selectRoom(Room room)
{
var drawable = roomFlow.FirstOrDefault(r => r.Room == room);
if (drawable != null && drawable.State == SelectionState.Selected)
JoinRequested?.Invoke(room);
else
roomFlow.Children.ForEach(r => r.State = r.Room == room ? SelectionState.Selected : SelectionState.NotSelected);
selectedRoom.Value = room;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (roomManager != null)
roomManager.RoomsUpdated -= updateSorting;
}
}
}

View File

@ -0,0 +1,151 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input.Events;
using osu.Framework.Screens;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Lounge.Components;
using osu.Game.Screens.Multi.Match;
namespace osu.Game.Screens.Multi.Lounge
{
public class LoungeSubScreen : MultiplayerSubScreen
{
protected readonly FilterControl Filter;
private readonly Container content;
private readonly RoomsContainer rooms;
private readonly Action<Screen> pushGameplayScreen;
private readonly ProcessingOverlay processingOverlay;
[Resolved(CanBeNull = true)]
private IRoomManager roomManager { get; set; }
public override string Title => "Lounge";
protected override Drawable TransitionContent => content;
public LoungeSubScreen(Action<Screen> pushGameplayScreen)
{
this.pushGameplayScreen = pushGameplayScreen;
RoomInspector inspector;
Children = new Drawable[]
{
Filter = new FilterControl { Depth = -1 },
content = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Width = 0.55f,
Children = new Drawable[]
{
new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
ScrollbarOverlapsContent = false,
Padding = new MarginPadding(10),
Child = new SearchContainer
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Child = rooms = new RoomsContainer { JoinRequested = joinRequested }
},
},
processingOverlay = new ProcessingOverlay { Alpha = 0 }
}
},
inspector = new RoomInspector
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Both,
Width = 0.45f,
},
},
},
};
inspector.Room.BindTo(rooms.SelectedRoom);
Filter.Search.Current.ValueChanged += s => filterRooms();
Filter.Tabs.Current.ValueChanged += t => filterRooms();
Filter.Search.Exit += Exit;
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
content.Padding = new MarginPadding
{
Top = Filter.DrawHeight,
Left = SearchableListOverlay.WIDTH_PADDING - DrawableRoom.SELECTION_BORDER_WIDTH,
Right = SearchableListOverlay.WIDTH_PADDING,
};
}
protected override void OnFocus(FocusEvent e)
{
GetContainingInputManager().ChangeFocus(Filter.Search);
}
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
Filter.Search.HoldFocus = true;
}
protected override bool OnExiting(Screen next)
{
Filter.Search.HoldFocus = false;
// no base call; don't animate
return false;
}
protected override void OnSuspending(Screen next)
{
base.OnSuspending(next);
Filter.Search.HoldFocus = false;
}
private void filterRooms()
{
rooms.Filter(Filter.CreateCriteria());
roomManager?.Filter(Filter.CreateCriteria());
}
private void joinRequested(Room room)
{
processingOverlay.Show();
roomManager?.JoinRoom(room, r =>
{
Push(room);
processingOverlay.Hide();
}, _ => processingOverlay.Hide());
}
/// <summary>
/// Push a room as a new subscreen.
/// </summary>
public void Push(Room room)
{
// Handles the case where a room is clicked 3 times in quick succession
if (!IsCurrentScreen)
return;
Push(new MatchSubScreen(room, s => pushGameplayScreen?.Invoke(s)));
}
}
}

View File

@ -9,17 +9,19 @@ using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Events; using osu.Framework.Input.Events;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Online.Multiplayer; using osu.Game.Online.Multiplayer;
using osu.Game.Online.Multiplayer.GameTypes;
using osu.Game.Screens.Multi.Components; using osu.Game.Screens.Multi.Components;
using osuTK; using osuTK;
namespace osu.Game.Screens.Multi.Screens.Match.Settings namespace osu.Game.Screens.Multi.Match.Components
{ {
public class GameTypePicker : TabControl<GameType> public class GameTypePicker : DisableableTabControl<GameType>
{ {
private const float height = 40; private const float height = 40;
private const float selection_width = 3; private const float selection_width = 3;
protected override TabItem<GameType> CreateTabItem(GameType value) => new GameTypePickerItem(value); protected override TabItem<GameType> CreateTabItem(GameType value) => new GameTypePickerItem(value);
protected override Dropdown<GameType> CreateDropdown() => null; protected override Dropdown<GameType> CreateDropdown() => null;
public GameTypePicker() public GameTypePicker()
@ -31,15 +33,17 @@ namespace osu.Game.Screens.Multi.Screens.Match.Settings
AddItem(new GameTypeVersus()); AddItem(new GameTypeVersus());
AddItem(new GameTypeTagTeam()); AddItem(new GameTypeTagTeam());
AddItem(new GameTypeTeamVersus()); AddItem(new GameTypeTeamVersus());
AddItem(new GameTypeTimeshift());
} }
private class GameTypePickerItem : TabItem<GameType> private class GameTypePickerItem : DisableableTabItem<GameType>
{ {
private const float transition_duration = 200; private const float transition_duration = 200;
private readonly CircularContainer hover, selection; private readonly CircularContainer hover, selection;
public GameTypePickerItem(GameType value) : base(value) public GameTypePickerItem(GameType value)
: base(value)
{ {
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;

View File

@ -0,0 +1,147 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
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.Drawables;
using osu.Game.Graphics;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using osu.Game.Screens.Play.HUD;
using osuTK;
using osuTK.Graphics;
namespace osu.Game.Screens.Multi.Match.Components
{
public class Header : Container
{
public const float HEIGHT = 200;
private readonly RoomBindings bindings = new RoomBindings();
private readonly Box tabStrip;
public readonly MatchTabControl Tabs;
public Action OnRequestSelectBeatmap;
public Header(Room room)
{
RelativeSizeAxes = Axes.X;
Height = HEIGHT;
bindings.Room = room;
BeatmapTypeInfo beatmapTypeInfo;
BeatmapSelectButton beatmapButton;
UpdateableBeatmapBackgroundSprite background;
ModDisplay modDisplay;
Children = new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Masking = true,
Children = new Drawable[]
{
background = new HeaderBeatmapBackgroundSprite { RelativeSizeAxes = Axes.Both },
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = ColourInfo.GradientVertical(Color4.Black.Opacity(0.4f), Color4.Black.Opacity(0.6f)),
},
}
},
tabStrip = new Box
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = 1,
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 20 },
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
beatmapTypeInfo = new BeatmapTypeInfo(),
modDisplay = new ModDisplay
{
Scale = new Vector2(0.75f),
DisplayUnrankedText = false
},
}
},
new Container
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
RelativeSizeAxes = Axes.Y,
Width = 200,
Padding = new MarginPadding { Vertical = 10 },
Child = beatmapButton = new BeatmapSelectButton(room)
{
RelativeSizeAxes = Axes.Both,
Height = 1,
},
},
Tabs = new MatchTabControl(room)
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X
},
},
},
};
beatmapTypeInfo.Beatmap.BindTo(bindings.CurrentBeatmap);
beatmapTypeInfo.Ruleset.BindTo(bindings.CurrentRuleset);
beatmapTypeInfo.Type.BindTo(bindings.Type);
background.Beatmap.BindTo(bindings.CurrentBeatmap);
bindings.CurrentMods.BindValueChanged(m => modDisplay.Current.Value = m, true);
beatmapButton.Action = () => OnRequestSelectBeatmap?.Invoke();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
tabStrip.Colour = colours.Yellow;
}
private class BeatmapSelectButton : HeaderButton
{
private readonly IBindable<int?> roomIDBind = new Bindable<int?>();
public BeatmapSelectButton(Room room)
{
Text = "Select beatmap";
roomIDBind.BindTo(room.RoomID);
roomIDBind.BindValueChanged(v => this.FadeTo(v.HasValue ? 0 : 1), true);
}
}
private class HeaderBeatmapBackgroundSprite : UpdateableBeatmapBackgroundSprite
{
protected override double FadeDuration => 200;
}
}
}

View File

@ -0,0 +1,48 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Screens.Multi.Match.Components
{
public class HeaderButton : TriangleButton
{
[BackgroundDependencyLoader]
private void load()
{
BackgroundColour = OsuColour.FromHex(@"1187aa");
Triangles.ColourLight = OsuColour.FromHex(@"277b9c");
Triangles.ColourDark = OsuColour.FromHex(@"1f6682");
Triangles.TriangleScale = 1.5f;
Add(new Container
{
RelativeSizeAxes = Axes.Both,
Alpha = 1f,
Child = new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0.15f,
Blending = BlendingMode.Additive,
},
});
}
protected override SpriteText CreateText() => new OsuSpriteText
{
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Font = @"Exo2.0-Light",
TextSize = 30,
};
}
}

View File

@ -0,0 +1,60 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Containers;
using osu.Game.Online.Chat;
using osu.Game.Users;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public class HostInfo : CompositeDrawable
{
public readonly IBindable<User> Host = new Bindable<User>();
private readonly LinkFlowContainer linkContainer;
private readonly UpdateableAvatar avatar;
public HostInfo()
{
AutoSizeAxes = Axes.X;
Height = 50;
InternalChild = new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Horizontal,
Spacing = new Vector2(5, 0),
Children = new Drawable[]
{
avatar = new UpdateableAvatar { Size = new Vector2(50) },
new FillFlowContainer
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Child = linkContainer = new LinkFlowContainer { AutoSizeAxes = Axes.Both }
}
}
};
Host.BindValueChanged(updateHost);
}
private void updateHost(User host)
{
avatar.User = host;
if (host != null)
{
linkContainer.AddText("hosted by");
linkContainer.NewLine();
linkContainer.AddLink(host.Username, null, LinkAction.OpenUserProfile, host.Id.ToString(), "View Profile", s => s.Font = "Exo2.0-BoldItalic");
}
}
}
}

View File

@ -0,0 +1,100 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Multiplayer;
using osu.Game.Overlays.SearchableList;
using osu.Game.Screens.Multi.Components;
using osuTK;
namespace osu.Game.Screens.Multi.Match.Components
{
public class Info : Container
{
public Action OnStart;
private readonly RoomBindings bindings = new RoomBindings();
public Info(Room room)
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
ReadyButton readyButton;
ViewBeatmapButton viewBeatmapButton;
HostInfo hostInfo;
RoomStatusInfo statusInfo;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.FromHex(@"28242d"),
},
new Container
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Horizontal = SearchableListOverlay.WIDTH_PADDING },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Spacing = new Vector2(0, 10),
Padding = new MarginPadding { Vertical = 20 },
Children = new Drawable[]
{
new FillFlowContainer
{
AutoSizeAxes = Axes.Both,
Direction = FillDirection.Vertical,
Children = new Drawable[]
{
new OsuSpriteText
{
TextSize = 30,
Current = bindings.Name
},
new RoomStatusInfo(room),
}
},
hostInfo = new HostInfo(),
},
},
new FillFlowContainer
{
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
AutoSizeAxes = Axes.X,
Height = 70,
Spacing = new Vector2(10, 0),
Direction = FillDirection.Horizontal,
Children = new Drawable[]
{
viewBeatmapButton = new ViewBeatmapButton(),
readyButton = new ReadyButton(room)
{
Action = () => OnStart?.Invoke()
}
}
}
},
},
};
viewBeatmapButton.Beatmap.BindTo(bindings.CurrentBeatmap);
readyButton.Beatmap.BindTo(bindings.CurrentBeatmap);
hostInfo.Host.BindTo(bindings.Host);
bindings.Room = room;
}
}
}

View File

@ -0,0 +1,36 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Online.Chat;
using osu.Game.Online.Multiplayer;
namespace osu.Game.Screens.Multi.Match.Components
{
public class MatchChatDisplay : StandAloneChatDisplay
{
private readonly Room room;
[Resolved(CanBeNull = true)]
private ChannelManager channelManager { get; set; }
public MatchChatDisplay(Room room)
: base(true)
{
this.room = room;
}
protected override void LoadComplete()
{
base.LoadComplete();
room.RoomID.BindValueChanged(v => updateChannel(), true);
}
private void updateChannel()
{
if (room.RoomID.Value != null)
Channel.Value = channelManager?.JoinChannel(new Channel { Id = room.ChannelId, Type = ChannelType.Multiplayer, Name = $"#mp_{room.RoomID}" });
}
}
}

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