diff --git a/README.md b/README.md
index c4d676f4be..56491a4be4 100644
--- a/README.md
+++ b/README.md
@@ -19,8 +19,9 @@ Detailed changelogs are published on the [official osu! site](https://osu.ppy.sh
## Requirements
- A desktop platform with the [.NET Core SDK 2.2](https://www.microsoft.com/net/learn/get-started) or higher installed.
+- When running on linux, please have a system-wide ffmpeg installation available to support video decoding.
+- When running on Windows 7 or 8.1, **[additional prerequisites](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** may be required to correctly run .NET Core applications if your operating system is not up-to-date with the latest service packs.
- When working with the codebase, we recommend using an IDE with intellisense and syntax highlighting, such as [Visual Studio 2017+](https://visualstudio.microsoft.com/vs/), [Jetbrains Rider](https://www.jetbrains.com/rider/) or [Visual Studio Code](https://code.visualstudio.com/).
-- Note that there are **[additional requirements for Windows 7 and Windows 8.1](https://docs.microsoft.com/en-us/dotnet/core/windows-prerequisites?tabs=netcore2x)** which you may need to manually install if your operating system is not up-to-date.
## Running osu!
diff --git a/osu.Android.props b/osu.Android.props
index 721d341c08..85741fcf84 100644
--- a/osu.Android.props
+++ b/osu.Android.props
@@ -63,6 +63,6 @@
-
+
diff --git a/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
new file mode 100644
index 0000000000..df79584167
--- /dev/null
+++ b/osu.Game.Tests/Visual/Menus/TestSceneIntroTriangles.cs
@@ -0,0 +1,15 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using NUnit.Framework;
+using osu.Framework.Screens;
+using osu.Game.Screens.Menu;
+
+namespace osu.Game.Tests.Visual.Menus
+{
+ [TestFixture]
+ public class TestSceneIntroTriangles : IntroTestScene
+ {
+ protected override IScreen CreateScreen() => new IntroTriangles();
+ }
+}
diff --git a/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
new file mode 100644
index 0000000000..d09a50b12c
--- /dev/null
+++ b/osu.Game.Tests/Visual/Online/TestSceneUserProfilePreviousUsernames.cs
@@ -0,0 +1,69 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using NUnit.Framework;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Game.Online.API;
+using osu.Game.Online.API.Requests;
+using osu.Game.Overlays.Profile.Header.Components;
+using osu.Game.Users;
+
+namespace osu.Game.Tests.Visual.Online
+{
+ [TestFixture]
+ public class TestSceneUserProfilePreviousUsernames : OsuTestScene
+ {
+ public override IReadOnlyList RequiredTypes => new[]
+ {
+ typeof(PreviousUsernames)
+ };
+
+ [Resolved]
+ private IAPIProvider api { get; set; }
+
+ private readonly Bindable user = new Bindable();
+
+ public TestSceneUserProfilePreviousUsernames()
+ {
+ Child = new PreviousUsernames
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ User = { BindTarget = user },
+ };
+
+ User[] users =
+ {
+ new User { PreviousUsernames = new[] { "username1" } },
+ new User { PreviousUsernames = new[] { "longusername", "longerusername" } },
+ new User { PreviousUsernames = new[] { "test", "angelsim", "verylongusername" } },
+ new User { PreviousUsernames = new[] { "ihavenoidea", "howcani", "makethistext", "anylonger" } },
+ new User { PreviousUsernames = new string[0] },
+ null
+ };
+
+ AddStep("single username", () => user.Value = users[0]);
+ AddStep("two usernames", () => user.Value = users[1]);
+ AddStep("three usernames", () => user.Value = users[2]);
+ AddStep("four usernames", () => user.Value = users[3]);
+ AddStep("no username", () => user.Value = users[4]);
+ AddStep("null user", () => user.Value = users[5]);
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ AddStep("online user (Angelsim)", () =>
+ {
+ var request = new GetUserRequest(1777162);
+ request.Success += user => this.user.Value = user;
+ api.Queue(request);
+ });
+ }
+ }
+}
diff --git a/osu.Game/Configuration/IntroSequence.cs b/osu.Game/Configuration/IntroSequence.cs
new file mode 100644
index 0000000000..1eb953be36
--- /dev/null
+++ b/osu.Game/Configuration/IntroSequence.cs
@@ -0,0 +1,11 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+namespace osu.Game.Configuration
+{
+ public enum IntroSequence
+ {
+ Circles,
+ Triangles
+ }
+}
diff --git a/osu.Game/Configuration/OsuConfigManager.cs b/osu.Game/Configuration/OsuConfigManager.cs
index 1da7c7ec1d..19f46c1d6a 100644
--- a/osu.Game/Configuration/OsuConfigManager.cs
+++ b/osu.Game/Configuration/OsuConfigManager.cs
@@ -105,6 +105,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.ScalingPositionY, 0.5f, 0f, 1f);
Set(OsuSetting.UIScale, 1f, 0.8f, 1.6f, 0.01f);
+
+ Set(OsuSetting.IntroSequence, IntroSequence.Triangles);
}
public OsuConfigManager(Storage storage)
@@ -167,6 +169,7 @@ namespace osu.Game.Configuration
ScalingPositionY,
ScalingSizeX,
ScalingSizeY,
- UIScale
+ UIScale,
+ IntroSequence
}
}
diff --git a/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs
new file mode 100644
index 0000000000..f18f319e27
--- /dev/null
+++ b/osu.Game/Overlays/Profile/Header/Components/PreviousUsernames.cs
@@ -0,0 +1,168 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Linq;
+using osu.Framework.Allocation;
+using osu.Framework.Bindables;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Sprites;
+using osu.Framework.Input.Events;
+using osu.Game.Graphics;
+using osu.Game.Users;
+using osuTK;
+
+namespace osu.Game.Overlays.Profile.Header.Components
+{
+ public class PreviousUsernames : CompositeDrawable
+ {
+ private const int duration = 200;
+ private const int margin = 10;
+ private const int width = 310;
+ private const int move_offset = 15;
+
+ public readonly Bindable User = new Bindable();
+
+ private readonly TextFlowContainer text;
+ private readonly Box background;
+ private readonly SpriteText header;
+
+ public PreviousUsernames()
+ {
+ HoverIconContainer hoverIcon;
+
+ AutoSizeAxes = Axes.Y;
+ Width = width;
+ Masking = true;
+ CornerRadius = 5;
+
+ AddRangeInternal(new Drawable[]
+ {
+ background = new Box
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ new GridContainer
+ {
+ AutoSizeAxes = Axes.Y,
+ RelativeSizeAxes = Axes.X,
+ RowDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.AutoSize)
+ },
+ ColumnDimensions = new[]
+ {
+ new Dimension(GridSizeMode.AutoSize),
+ new Dimension(GridSizeMode.Distributed)
+ },
+ Content = new[]
+ {
+ new Drawable[]
+ {
+ hoverIcon = new HoverIconContainer(),
+ header = new SpriteText
+ {
+ Anchor = Anchor.BottomLeft,
+ Origin = Anchor.BottomLeft,
+ Text = @"formerly known as",
+ Font = OsuFont.GetFont(size: 10, italics: true)
+ }
+ },
+ new Drawable[]
+ {
+ new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ },
+ text = new TextFlowContainer(s => s.Font = OsuFont.GetFont(size: 12, weight: FontWeight.Bold, italics: true))
+ {
+ RelativeSizeAxes = Axes.X,
+ AutoSizeAxes = Axes.Y,
+ Direction = FillDirection.Full,
+ Margin = new MarginPadding { Bottom = margin, Top = margin / 2f }
+ }
+ }
+ }
+ }
+ });
+
+ hoverIcon.ActivateHover += showContent;
+ hideContent();
+ }
+
+ [BackgroundDependencyLoader]
+ private void load(OsuColour colours)
+ {
+ background.Colour = colours.GreySeafoamDarker;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ User.BindValueChanged(onUserChanged, true);
+ }
+
+ private void onUserChanged(ValueChangedEvent user)
+ {
+ text.Text = string.Empty;
+
+ var usernames = user.NewValue?.PreviousUsernames;
+
+ if (usernames?.Any() ?? false)
+ {
+ text.Text = string.Join(", ", usernames);
+ Show();
+ return;
+ }
+
+ Hide();
+ }
+
+ protected override void OnHoverLost(HoverLostEvent e)
+ {
+ base.OnHoverLost(e);
+ hideContent();
+ }
+
+ private void showContent()
+ {
+ text.FadeIn(duration, Easing.OutQuint);
+ header.FadeIn(duration, Easing.OutQuint);
+ background.FadeIn(duration, Easing.OutQuint);
+ this.MoveToY(-move_offset, duration, Easing.OutQuint);
+ }
+
+ private void hideContent()
+ {
+ text.FadeOut(duration, Easing.OutQuint);
+ header.FadeOut(duration, Easing.OutQuint);
+ background.FadeOut(duration, Easing.OutQuint);
+ this.MoveToY(0, duration, Easing.OutQuint);
+ }
+
+ private class HoverIconContainer : Container
+ {
+ public Action ActivateHover;
+
+ public HoverIconContainer()
+ {
+ AutoSizeAxes = Axes.Both;
+ Child = new SpriteIcon
+ {
+ Margin = new MarginPadding { Top = 6, Left = margin, Right = margin * 2 },
+ Size = new Vector2(15),
+ Icon = FontAwesome.Solid.IdCard,
+ };
+ }
+
+ protected override bool OnHover(HoverEvent e)
+ {
+ ActivateHover?.Invoke();
+ return base.OnHover(e);
+ }
+ }
+ }
+}
diff --git a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
index 4e43caff23..5ccdc952ba 100644
--- a/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
+++ b/osu.Game/Overlays/Settings/Sections/Audio/MainMenuSettings.cs
@@ -1,7 +1,10 @@
// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
// See the LICENCE file in the repository root for full licence text.
+using System;
+using System.Linq;
using osu.Framework.Allocation;
+using osu.Framework.Graphics;
using osu.Game.Configuration;
namespace osu.Game.Overlays.Settings.Sections.Audio
@@ -13,7 +16,7 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
- Children = new[]
+ Children = new Drawable[]
{
new SettingsCheckbox
{
@@ -25,6 +28,12 @@ namespace osu.Game.Overlays.Settings.Sections.Audio
LabelText = "osu! music theme",
Bindable = config.GetBindable(OsuSetting.MenuMusic)
},
+ new SettingsDropdown
+ {
+ LabelText = "Intro sequence",
+ Bindable = config.GetBindable(OsuSetting.IntroSequence),
+ Items = Enum.GetValues(typeof(IntroSequence)).Cast()
+ },
};
}
}
diff --git a/osu.Game/Screens/BackgroundScreen.cs b/osu.Game/Screens/BackgroundScreen.cs
index bbe162cf7c..5dfaceccf5 100644
--- a/osu.Game/Screens/BackgroundScreen.cs
+++ b/osu.Game/Screens/BackgroundScreen.cs
@@ -11,8 +11,11 @@ namespace osu.Game.Screens
{
public abstract class BackgroundScreen : Screen, IEquatable
{
- protected BackgroundScreen()
+ private readonly bool animateOnEnter;
+
+ protected BackgroundScreen(bool animateOnEnter = true)
{
+ this.animateOnEnter = animateOnEnter;
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}
@@ -39,11 +42,14 @@ namespace osu.Game.Screens
public override void OnEntering(IScreen last)
{
- this.FadeOut();
- this.MoveToX(x_movement_amount);
+ if (animateOnEnter)
+ {
+ this.FadeOut();
+ this.MoveToX(x_movement_amount);
- this.FadeIn(transition_length, Easing.InOutQuart);
- this.MoveToX(0, transition_length, Easing.InOutQuart);
+ this.FadeIn(transition_length, Easing.InOutQuart);
+ this.MoveToX(0, transition_length, Easing.InOutQuart);
+ }
base.OnEntering(last);
}
diff --git a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
index 55338ea01a..2d7fe6a6a3 100644
--- a/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
+++ b/osu.Game/Screens/Backgrounds/BackgroundScreenDefault.cs
@@ -25,6 +25,11 @@ namespace osu.Game.Screens.Backgrounds
private Bindable user;
private Bindable skin;
+ public BackgroundScreenDefault(bool animateOnEnter = true)
+ : base(animateOnEnter)
+ {
+ }
+
[BackgroundDependencyLoader]
private void load(IAPIProvider api, SkinManager skinManager)
{
diff --git a/osu.Game/Screens/Loader.cs b/osu.Game/Screens/Loader.cs
index de00ba2e9f..850349272e 100644
--- a/osu.Game/Screens/Loader.cs
+++ b/osu.Game/Screens/Loader.cs
@@ -9,6 +9,8 @@ using osu.Framework.Graphics.Shaders;
using osu.Game.Screens.Menu;
using osuTK;
using osu.Framework.Screens;
+using osu.Game.Configuration;
+using IntroSequence = osu.Game.Configuration.IntroSequence;
namespace osu.Game.Screens
{
@@ -45,6 +47,8 @@ namespace osu.Game.Screens
private OsuScreen loadableScreen;
private ShaderPrecompiler precompiler;
+ private IntroSequence introSequence;
+
protected virtual OsuScreen CreateLoadableScreen()
{
if (showDisclaimer)
@@ -53,7 +57,17 @@ namespace osu.Game.Screens
return getIntroSequence();
}
- private IntroScreen getIntroSequence() => new IntroCircles();
+ private IntroScreen getIntroSequence()
+ {
+ switch (introSequence)
+ {
+ case IntroSequence.Circles:
+ return new IntroCircles();
+
+ default:
+ return new IntroTriangles();
+ }
+ }
protected virtual ShaderPrecompiler CreateShaderPrecompiler() => new ShaderPrecompiler();
@@ -79,9 +93,10 @@ namespace osu.Game.Screens
}
[BackgroundDependencyLoader]
- private void load(OsuGameBase game)
+ private void load(OsuGameBase game, OsuConfigManager config)
{
showDisclaimer = game.IsDeployedBuild;
+ introSequence = config.Get(OsuSetting.IntroSequence);
}
///
diff --git a/osu.Game/Screens/Menu/IntroTriangles.cs b/osu.Game/Screens/Menu/IntroTriangles.cs
new file mode 100644
index 0000000000..ba0d624959
--- /dev/null
+++ b/osu.Game/Screens/Menu/IntroTriangles.cs
@@ -0,0 +1,413 @@
+// Copyright (c) ppy Pty Ltd . Licensed under the MIT Licence.
+// See the LICENCE file in the repository root for full licence text.
+
+using System;
+using System.Collections.Generic;
+using System.IO;
+using osu.Framework.Allocation;
+using osu.Framework.Audio;
+using osu.Framework.Audio.Sample;
+using osu.Framework.Audio.Track;
+using osu.Framework.Bindables;
+using osu.Framework.Screens;
+using osu.Framework.Graphics;
+using osu.Framework.Graphics.Containers;
+using osu.Framework.Graphics.Shapes;
+using osu.Framework.Graphics.Textures;
+using osu.Framework.Graphics.Video;
+using osu.Framework.MathUtils;
+using osu.Framework.Timing;
+using osu.Game.Beatmaps;
+using osu.Game.Configuration;
+using osu.Game.Graphics;
+using osu.Game.Graphics.Containers;
+using osu.Game.Graphics.Sprites;
+using osu.Game.IO.Archives;
+using osu.Game.Rulesets;
+using osu.Game.Screens.Backgrounds;
+using osuTK;
+using osuTK.Graphics;
+
+namespace osu.Game.Screens.Menu
+{
+ public class IntroTriangles : IntroScreen
+ {
+ private const string menu_music_beatmap_hash = "a1556d0801b3a6b175dda32ef546f0ec812b400499f575c44fccbe9c67f9b1e5";
+
+ private SampleChannel welcome;
+
+ protected override BackgroundScreen CreateBackground() => background = new BackgroundScreenDefault(false)
+ {
+ Alpha = 0,
+ };
+
+ [Resolved]
+ private AudioManager audio { get; set; }
+
+ private Bindable menuMusic;
+ private Track track;
+ private WorkingBeatmap introBeatmap;
+
+ private BackgroundScreenDefault background;
+
+ [BackgroundDependencyLoader]
+ private void load(OsuConfigManager config, BeatmapManager beatmaps, Framework.Game game)
+ {
+ menuMusic = config.GetBindable(OsuSetting.MenuMusic);
+
+ BeatmapSetInfo setInfo = null;
+
+ if (!menuMusic.Value)
+ {
+ var sets = beatmaps.GetAllUsableBeatmapSets();
+ if (sets.Count > 0)
+ setInfo = beatmaps.QueryBeatmapSet(s => s.ID == sets[RNG.Next(0, sets.Count - 1)].ID);
+ }
+
+ if (setInfo == null)
+ {
+ setInfo = beatmaps.QueryBeatmapSet(b => b.Hash == menu_music_beatmap_hash);
+
+ if (setInfo == null)
+ {
+ // we need to import the default menu background beatmap
+ setInfo = beatmaps.Import(new ZipArchiveReader(game.Resources.GetStream(@"Tracks/triangles.osz"), "triangles.osz")).Result;
+
+ setInfo.Protected = true;
+ beatmaps.Update(setInfo);
+ }
+ }
+
+ introBeatmap = beatmaps.GetWorkingBeatmap(setInfo.Beatmaps[0]);
+
+ track = introBeatmap.Track;
+ track.Reset();
+
+ if (config.Get(OsuSetting.MenuVoice) && !menuMusic.Value)
+ // triangles has welcome sound included in the track. only play this if the user doesn't want menu music.
+ welcome = audio.Samples.Get(@"welcome");
+ }
+
+ protected override void LogoArriving(OsuLogo logo, bool resuming)
+ {
+ base.LogoArriving(logo, resuming);
+
+ logo.Triangles = true;
+
+ if (!resuming)
+ {
+ Beatmap.Value = introBeatmap;
+ introBeatmap = null;
+
+ PrepareMenuLoad();
+
+ LoadComponentAsync(new TrianglesIntroSequence(logo, background)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Clock = new FramedClock(menuMusic.Value ? track : null),
+ LoadMenu = LoadMenu
+ }, t =>
+ {
+ AddInternal(t);
+ welcome?.Play();
+
+ // Only start the current track if it is the menu music. A beatmap's track is started when entering the Main Menu.
+ if (menuMusic.Value)
+ track.Start();
+ });
+ }
+ }
+
+ public override void OnResuming(IScreen last)
+ {
+ base.OnResuming(last);
+ background.FadeOut(100);
+ }
+
+ public override void OnSuspending(IScreen next)
+ {
+ track = null;
+ base.OnSuspending(next);
+ }
+
+ private class TrianglesIntroSequence : CompositeDrawable
+ {
+ private readonly OsuLogo logo;
+ private readonly BackgroundScreenDefault background;
+ private OsuSpriteText welcomeText;
+
+ private RulesetFlow rulesets;
+ private Container rulesetsScale;
+ private Drawable logoContainerSecondary;
+ private Drawable logoContainer;
+
+ private GlitchingTriangles triangles;
+
+ public Action LoadMenu;
+
+ public TrianglesIntroSequence(OsuLogo logo, BackgroundScreenDefault background)
+ {
+ this.logo = logo;
+ this.background = background;
+ }
+
+ private OsuGameBase game;
+
+ [BackgroundDependencyLoader]
+ private void load(TextureStore textures, OsuGameBase game)
+ {
+ this.game = game;
+
+ InternalChildren = new[]
+ {
+ triangles = new GlitchingTriangles
+ {
+ Alpha = 0,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Size = new Vector2(0.4f, 0.16f)
+ },
+ welcomeText = new OsuSpriteText
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Padding = new MarginPadding { Bottom = 10 },
+ Font = OsuFont.GetFont(weight: FontWeight.Light, size: 42),
+ Alpha = 1,
+ Spacing = new Vector2(5),
+ },
+ rulesetsScale = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Children = new Drawable[]
+ {
+ rulesets = new RulesetFlow()
+ }
+ },
+ logoContainerSecondary = new Container
+ {
+ RelativeSizeAxes = Axes.Both,
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Child = logoContainer = new LazerLogo(textures.GetStream("Menu/logo-triangles.mp4"))
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ }
+ },
+ };
+ }
+
+ private const double text_1 = 200;
+ private const double text_2 = 400;
+ private const double text_3 = 700;
+ private const double text_4 = 900;
+ private const double text_glitch = 1060;
+
+ private const double rulesets_1 = 1450;
+ private const double rulesets_2 = 1650;
+ private const double rulesets_3 = 1850;
+
+ private const double logo_scale_duration = 920;
+ private const double logo_1 = 2080;
+ private const double logo_2 = logo_1 + logo_scale_duration;
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+
+ const float scale_start = 1.2f;
+ const float scale_adjust = 0.8f;
+
+ rulesets.Hide();
+ logoContainer.Hide();
+ background.Hide();
+
+ using (BeginAbsoluteSequence(0, true))
+ {
+ using (BeginDelayedSequence(text_1, true))
+ welcomeText.FadeIn().OnComplete(t => t.Text = "wel");
+
+ using (BeginDelayedSequence(text_2, true))
+ welcomeText.FadeIn().OnComplete(t => t.Text = "welcome");
+
+ using (BeginDelayedSequence(text_3, true))
+ welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to");
+
+ using (BeginDelayedSequence(text_4, true))
+ {
+ welcomeText.FadeIn().OnComplete(t => t.Text = "welcome to osu!");
+ welcomeText.TransformTo(nameof(welcomeText.Spacing), new Vector2(50, 0), 5000);
+ }
+
+ using (BeginDelayedSequence(text_glitch, true))
+ triangles.FadeIn();
+
+ using (BeginDelayedSequence(rulesets_1, true))
+ {
+ rulesetsScale.ScaleTo(0.8f, 1000);
+ rulesets.FadeIn().ScaleTo(1).TransformSpacingTo(new Vector2(200, 0));
+ welcomeText.FadeOut();
+ triangles.FadeOut();
+ }
+
+ using (BeginDelayedSequence(rulesets_2, true))
+ {
+ rulesets.ScaleTo(2).TransformSpacingTo(new Vector2(30, 0));
+ }
+
+ using (BeginDelayedSequence(rulesets_3, true))
+ {
+ rulesets.ScaleTo(4).TransformSpacingTo(new Vector2(10, 0));
+ rulesetsScale.ScaleTo(1.3f, 1000);
+ }
+
+ using (BeginDelayedSequence(logo_1, true))
+ {
+ rulesets.FadeOut();
+
+ // matching flyte curve y = 0.25x^2 + (max(0, x - 0.7) / 0.3) ^ 5
+ logoContainer.FadeIn().ScaleTo(scale_start).Then().Delay(logo_scale_duration * 0.7f).ScaleTo(scale_start - scale_adjust, logo_scale_duration * 0.3f, Easing.InQuint);
+ logoContainerSecondary.ScaleTo(scale_start).Then().ScaleTo(scale_start - scale_adjust * 0.25f, logo_scale_duration, Easing.InQuad);
+ }
+
+ using (BeginDelayedSequence(logo_2, true))
+ {
+ logoContainer.FadeOut().OnComplete(_ =>
+ {
+ logo.FadeIn();
+ background.FadeIn();
+
+ game.Add(new GameWideFlash());
+
+ LoadMenu();
+ });
+ }
+ }
+ }
+
+ private class GameWideFlash : Box
+ {
+ private const double flash_length = 1000;
+
+ public GameWideFlash()
+ {
+ Colour = Color4.White;
+ RelativeSizeAxes = Axes.Both;
+ Blending = BlendingMode.Additive;
+ }
+
+ protected override void LoadComplete()
+ {
+ base.LoadComplete();
+ this.FadeOutFromOne(flash_length, Easing.Out);
+ }
+ }
+
+ private class LazerLogo : CompositeDrawable
+ {
+ public LazerLogo(Stream videoStream)
+ {
+ Size = new Vector2(960);
+
+ InternalChild = new VideoSprite(videoStream)
+ {
+ RelativeSizeAxes = Axes.Both,
+ Clock = new FramedOffsetClock(Clock) { Offset = -logo_1 }
+ };
+ }
+ }
+
+ private class RulesetFlow : FillFlowContainer
+ {
+ [BackgroundDependencyLoader]
+ private void load(RulesetStore rulesets)
+ {
+ var modes = new List();
+
+ foreach (var ruleset in rulesets.AvailableRulesets)
+ {
+ var icon = new ConstrainedIconContainer
+ {
+ Icon = ruleset.CreateInstance().CreateIcon(),
+ Size = new Vector2(30),
+ };
+
+ modes.Add(icon);
+ }
+
+ AutoSizeAxes = Axes.Both;
+ Children = modes;
+
+ Anchor = Anchor.Centre;
+ Origin = Anchor.Centre;
+ }
+ }
+
+ private class GlitchingTriangles : CompositeDrawable
+ {
+ public GlitchingTriangles()
+ {
+ RelativeSizeAxes = Axes.Both;
+ }
+
+ private double? lastGenTime;
+
+ private const double time_between_triangles = 22;
+
+ protected override void Update()
+ {
+ base.Update();
+
+ if (lastGenTime == null || Time.Current - lastGenTime > time_between_triangles)
+ {
+ lastGenTime = (lastGenTime ?? Time.Current) + time_between_triangles;
+
+ Drawable triangle = new OutlineTriangle(RNG.NextBool(), (RNG.NextSingle() + 0.2f) * 80)
+ {
+ RelativePositionAxes = Axes.Both,
+ Position = new Vector2(RNG.NextSingle(), RNG.NextSingle()),
+ };
+
+ AddInternal(triangle);
+
+ triangle.FadeOutFromOne(120);
+ }
+ }
+
+ ///
+ /// Represents a sprite that is drawn in a triangle shape, instead of a rectangle shape.
+ ///
+ public class OutlineTriangle : BufferedContainer
+ {
+ public OutlineTriangle(bool outlineOnly, float size)
+ {
+ Size = new Vector2(size);
+
+ InternalChildren = new Drawable[]
+ {
+ new Triangle { RelativeSizeAxes = Axes.Both },
+ };
+
+ if (outlineOnly)
+ {
+ AddInternal(new Triangle
+ {
+ Anchor = Anchor.Centre,
+ Origin = Anchor.Centre,
+ Colour = Color4.Black,
+ Size = new Vector2(size - 5),
+ Blending = BlendingMode.None,
+ });
+ }
+
+ Blending = BlendingMode.Additive;
+ CacheDrawnFrameBuffer = true;
+ }
+ }
+ }
+ }
+ }
+}
diff --git a/osu.Game/Users/User.cs b/osu.Game/Users/User.cs
index a5f3578711..9986f70557 100644
--- a/osu.Game/Users/User.cs
+++ b/osu.Game/Users/User.cs
@@ -20,6 +20,9 @@ namespace osu.Game.Users
[JsonProperty(@"username")]
public string Username;
+ [JsonProperty(@"previous_usernames")]
+ public string[] PreviousUsernames;
+
[JsonProperty(@"country")]
public Country Country;
diff --git a/osu.Game/osu.Game.csproj b/osu.Game/osu.Game.csproj
index b5266fd75d..e149c4338d 100644
--- a/osu.Game/osu.Game.csproj
+++ b/osu.Game/osu.Game.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/osu.iOS.props b/osu.iOS.props
index 103d89cadc..26a8fa7647 100644
--- a/osu.iOS.props
+++ b/osu.iOS.props
@@ -105,12 +105,12 @@
-
-
+
+
-
+