This commit is contained in:
DrabWeb 2017-02-22 12:35:55 -04:00
commit 99f1f88c75
117 changed files with 1756 additions and 583 deletions

@ -1 +1 @@
Subproject commit 659cb2589252f502d2f29c1c1b3878b8f5910d86 Subproject commit de1568254c4c9a4ea540ccad94700c5c51f70dc2

View File

@ -9,7 +9,7 @@ using System.Threading.Tasks;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Desktop.Platform; using osu.Framework.Desktop.Platform;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Game; using osu.Game;
using osu.Game.Modes; using osu.Game.Modes;
using osu.Game.Modes.Catch; using osu.Game.Modes.Catch;

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using osu.Framework; using osu.Framework;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
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.Graphics.Sprites;
@ -17,6 +17,7 @@ using osu.Game.Online.Chat;
using OpenTK; using OpenTK;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Game.Online.Chat.Drawables; using osu.Game.Online.Chat.Drawables;
using osu.Game.Overlays;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests
{ {
@ -25,119 +26,16 @@ namespace osu.Desktop.VisualTests.Tests
private ScheduledDelegate messageRequest; private ScheduledDelegate messageRequest;
public override string Name => @"Chat"; public override string Name => @"Chat";
public override string Description => @"Testing API polling"; public override string Description => @"Testing chat api and overlay";
FlowContainer flow;
private Scheduler scheduler = new Scheduler();
private APIAccess api;
private ChannelDisplay channelDisplay;
[BackgroundDependencyLoader]
private void load(APIAccess api)
{
this.api = api;
}
public override void Reset() public override void Reset()
{ {
base.Reset(); base.Reset();
if (api.State != APIState.Online) Add(new ChatOverlay()
api.OnStateChange += delegate { initializeChannels(); };
else
initializeChannels();
}
protected override void Update()
{
scheduler.Update();
base.Update();
}
private long? lastMessageId;
List<Channel> careChannels;
private void initializeChannels()
{
careChannels = new List<Channel>();
if (api.State != APIState.Online)
return;
Add(flow = new FlowContainer
{ {
RelativeSizeAxes = Axes.Both, State = Visibility.Visible
Direction = FlowDirections.Vertical
}); });
SpriteText loading;
Add(loading = new SpriteText
{
Text = @"Loading available channels...",
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
TextSize = 40,
});
messageRequest?.Cancel();
ListChannelsRequest req = new ListChannelsRequest();
req.Success += delegate (List<Channel> channels)
{
Scheduler.Add(delegate
{
loading.FadeOut(100);
});
addChannel(channels.Find(c => c.Name == @"#osu"));
addChannel(channels.Find(c => c.Name == @"#lobby"));
addChannel(channels.Find(c => c.Name == @"#english"));
messageRequest = scheduler.AddDelayed(() => FetchNewMessages(api), 1000, true);
};
api.Queue(req);
}
private void addChannel(Channel channel)
{
flow.Add(channelDisplay = new ChannelDisplay(channel)
{
Size = new Vector2(1, 0.3f)
});
careChannels.Add(channel);
}
GetMessagesRequest fetchReq;
public void FetchNewMessages(APIAccess api)
{
if (fetchReq != null) return;
fetchReq = new GetMessagesRequest(careChannels, lastMessageId);
fetchReq.Success += delegate (List<Message> messages)
{
foreach (Message m in messages)
{
careChannels.Find(c => c.Id == m.ChannelId).AddNewMessages(m);
}
lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId;
Debug.Write("success!");
fetchReq = null;
};
fetchReq.Failure += delegate
{
Debug.Write("failure!");
fetchReq = null;
};
api.Queue(fetchReq);
} }
} }
} }

View File

@ -2,7 +2,7 @@
// 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.Collections.Generic;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Timing; using osu.Framework.Timing;

View File

@ -3,7 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework; using osu.Framework;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Timing; using osu.Framework.Timing;
using OpenTK; using OpenTK;
@ -37,7 +37,7 @@ namespace osu.Desktop.VisualTests.Tests
playbackSpeed.ValueChanged += delegate { rateAdjustClock.Rate = playbackSpeed.Value; }; playbackSpeed.ValueChanged += delegate { rateAdjustClock.Rate = playbackSpeed.Value; };
} }
HitObjectType mode = HitObjectType.Spinner; HitObjectType mode = HitObjectType.Slider;
BindableNumber<double> playbackSpeed = new BindableDouble(0.5) { MinValue = 0, MaxValue = 1 }; BindableNumber<double> playbackSpeed = new BindableDouble(0.5) { MinValue = 0, MaxValue = 1 };
private Container playfieldContainer; private Container playfieldContainer;
@ -75,6 +75,7 @@ namespace osu.Desktop.VisualTests.Tests
Length = 400, Length = 400,
Position = new Vector2(-200, 0), Position = new Vector2(-200, 0),
Velocity = 1, Velocity = 1,
TickDistance = 100,
})); }));
break; break;
case HitObjectType.Spinner: case HitObjectType.Spinner:

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using OpenTK.Input; using OpenTK.Input;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Screens.Menu; using osu.Game.Screens.Menu;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Overlays; using osu.Game.Overlays;

View File

@ -3,7 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Overlays; using osu.Game.Overlays;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
using OpenTK.Input; using OpenTK.Input;

View File

@ -9,7 +9,7 @@ using osu.Game.Overlays.Pause;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
namespace osu.Desktop.VisualTests.Tests namespace osu.Desktop.VisualTests.Tests

View File

@ -3,7 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Desktop.VisualTests.Platform; using osu.Desktop.VisualTests.Platform;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Game.Database; using osu.Game.Database;
using osu.Game.Modes; using osu.Game.Modes;
using osu.Game.Screens.Select; using osu.Game.Screens.Select;

View File

@ -3,7 +3,7 @@
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Framework.Timing; using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
@ -71,7 +71,7 @@ namespace osu.Desktop.VisualTests.Tests
Add(new Box Add(new Box
{ {
RelativeSizeAxes = Framework.Graphics.Axes.Both, RelativeSizeAxes = Framework.Graphics.Axes.Both,
Colour = Color4.Gray, Colour = Color4.Black,
}); });
Add(new Player Add(new Player

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics.Colour; using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework; using osu.Framework;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Game.Database; using osu.Game.Database;
using osu.Game; using osu.Game;
@ -21,7 +21,7 @@ namespace osu.Desktop.VisualTests
{ {
base.LoadComplete(); base.LoadComplete();
(new BackgroundModeDefault() { Depth = 10 }).Preload(this, AddInternal); (new BackgroundScreenDefault() { Depth = 10 }).Preload(this, AddInternal);
// Have to construct this here, rather than in the constructor, because // Have to construct this here, rather than in the constructor, because
// we depend on some dependencies to be loaded within OsuGameBase.load(). // we depend on some dependencies to be loaded within OsuGameBase.load().

View File

@ -14,22 +14,32 @@ using osu.Game.Database;
using osu.Desktop.Overlays; using osu.Desktop.Overlays;
using System.Reflection; using System.Reflection;
using System.Drawing; using System.Drawing;
using osu.Game.Screens.Menu;
namespace osu.Desktop namespace osu.Desktop
{ {
class OsuGameDesktop : OsuGame class OsuGameDesktop : OsuGame
{ {
private VersionManager versionManager;
public override bool IsDeployedBuild => versionManager.IsDeployedBuild;
public OsuGameDesktop(string[] args = null) public OsuGameDesktop(string[] args = null)
: base(args) : base(args)
{ {
versionManager = new VersionManager { Depth = int.MinValue };
} }
protected override void LoadComplete() protected override void LoadComplete()
{ {
base.LoadComplete(); base.LoadComplete();
(new VersionManager()).Preload(this, Add); versionManager.Preload(this);
ModeChanged += m =>
{
if (!versionManager.IsAlive && m is Intro)
Add(versionManager);
};
} }
public override void SetHost(BasicGameHost host) public override void SetHost(BasicGameHost host)

View File

@ -1,9 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays; using osu.Game.Overlays;
@ -15,13 +18,19 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Graphics; using osu.Game.Graphics;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using System.Net.Http;
using osu.Framework.Logging;
namespace osu.Desktop.Overlays namespace osu.Desktop.Overlays
{ {
public class VersionManager : OverlayContainer public class VersionManager : OverlayContainer
{ {
private UpdateManager updateManager; private UpdateManager updateManager;
private NotificationManager notification; private NotificationManager notificationManager;
AssemblyName assembly = Assembly.GetEntryAssembly().GetName();
public bool IsDeployedBuild => assembly.Version.Major > 0;
protected override bool HideOnEscape => false; protected override bool HideOnEscape => false;
@ -30,7 +39,7 @@ namespace osu.Desktop.Overlays
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(NotificationManager notification, OsuColour colours, TextureStore textures) private void load(NotificationManager notification, OsuColour colours, TextureStore textures)
{ {
this.notification = notification; this.notificationManager = notification;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre; Anchor = Anchor.BottomCentre;
@ -40,14 +49,13 @@ namespace osu.Desktop.Overlays
bool isDebug = false; bool isDebug = false;
Debug.Assert(isDebug = true); Debug.Assert(isDebug = true);
var asm = Assembly.GetEntryAssembly().GetName();
string version; string version;
if (asm.Version.Major == 0) if (!IsDeployedBuild)
{ {
version = @"local " + (isDebug ? @"debug" : @"release"); version = @"local " + (isDebug ? @"debug" : @"release");
} }
else else
version = $@"{asm.Version.Major}.{asm.Version.Minor}.{asm.Version.Build}"; version = $@"{assembly.Version.Major}.{assembly.Version.Minor}.{assembly.Version.Build}";
Children = new Drawable[] Children = new Drawable[]
{ {
@ -95,7 +103,8 @@ namespace osu.Desktop.Overlays
} }
}; };
updateChecker(); if (IsDeployedBuild)
updateChecker();
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -110,32 +119,71 @@ namespace osu.Desktop.Overlays
updateManager?.Dispose(); updateManager?.Dispose();
} }
private async void updateChecker() private async void updateChecker(bool useDeltaPatching = true, UpdateProgressNotification notification = null)
{ {
updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true); //should we schedule a retry on completion of this check?
bool scheduleRetry = true;
if (!updateManager.IsInstalledApp) try
return;
var info = await updateManager.CheckForUpdate();
if (info.ReleasesToApply.Count > 0)
{ {
ProgressNotification n = new UpdateProgressNotification if (updateManager == null) updateManager = await UpdateManager.GitHubUpdateManager(@"https://github.com/ppy/osu", @"osulazer", null, null, true);
var info = await updateManager.CheckForUpdate(!useDeltaPatching);
if (info.ReleasesToApply.Count == 0)
//no updates available. bail and retry later.
return;
if (notification == null)
{ {
Text = @"Downloading update..." notification = new UpdateProgressNotification { State = ProgressNotificationState.Active };
}; Schedule(() => notificationManager.Post(notification));
Schedule(() => notification.Post(n)); }
Schedule(() => n.State = ProgressNotificationState.Active);
await updateManager.DownloadReleases(info.ReleasesToApply, (int p) => Schedule(() => n.Progress = p / 100f));
Schedule(() => n.Text = @"Installing update...");
await updateManager.ApplyReleases(info, (int p) => Schedule(() => n.Progress = p / 100f));
Schedule(() => n.State = ProgressNotificationState.Completed);
Schedule(() =>
{
notification.Progress = 0;
notification.Text = @"Downloading update...";
});
try
{
await updateManager.DownloadReleases(info.ReleasesToApply, p => Schedule(() => notification.Progress = p / 100f));
Schedule(() =>
{
notification.Progress = 0;
notification.Text = @"Installing update...";
});
await updateManager.ApplyReleases(info, p => Schedule(() => notification.Progress = p / 100f));
Schedule(() => notification.State = ProgressNotificationState.Completed);
}
catch (Exception)
{
if (useDeltaPatching)
{
//could fail if deltas are unavailable for full update path (https://github.com/Squirrel/Squirrel.Windows/issues/959)
//try again without deltas.
updateChecker(false, notification);
scheduleRetry = false;
}
}
} }
else catch (HttpRequestException)
{ {
//check again every 30 minutes. //likely have no internet connection.
Scheduler.AddDelayed(updateChecker, 60000 * 30); //we'll ignore this and retry later.
}
finally
{
if (scheduleRetry)
{
//check again in 30 minutes.
Scheduler.AddDelayed(() => updateChecker(), 60000 * 30);
if (notification != null)
notification.State = ProgressNotificationState.Cancelled;
}
} }
} }
@ -159,6 +207,25 @@ namespace osu.Desktop.Overlays
return true; return true;
} }
}; };
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
IconContent.Add(new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
ColourInfo = ColourInfo.GradientVertical(colours.YellowDark, colours.Yellow)
},
new TextAwesome
{
Anchor = Anchor.Centre,
Icon = FontAwesome.fa_upload,
Colour = Color4.White,
}
});
}
} }
} }
} }

View File

@ -137,6 +137,16 @@
</Reference> </Reference>
<Reference Include="System" /> <Reference Include="System" />
<Reference Include="System.Drawing" /> <Reference Include="System.Drawing" />
<Reference Include="System.Net.Http" />
<Reference Include="System.Net.Http.Extensions, Version=2.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Extensions.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.Primitives, Version=4.2.29.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a, processorArchitecture=MSIL">
<HintPath>..\packages\Microsoft.Net.Http.2.2.29\lib\net45\System.Net.Http.Primitives.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="System.Net.Http.WebRequest" />
<Reference Include="System.Windows.Forms" /> <Reference Include="System.Windows.Forms" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
@ -219,7 +229,6 @@
<ItemGroup> <ItemGroup>
<Content Include="lazer.ico" /> <Content Include="lazer.ico" />
</ItemGroup> </ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" /> <Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- To modify your build process, add your task inside one of the targets below and uncomment it. <!-- To modify your build process, add your task inside one of the targets below and uncomment it.
Other similar extension points exist, see Microsoft.Common.targets. Other similar extension points exist, see Microsoft.Common.targets.

View File

@ -5,6 +5,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
--> -->
<packages> <packages>
<package id="DeltaCompressionDotNet" version="1.0.0" targetFramework="net45" /> <package id="DeltaCompressionDotNet" version="1.0.0" targetFramework="net45" />
<package id="Microsoft.Net.Http" version="2.2.29" targetFramework="net45" />
<package id="Mono.Cecil" version="0.9.6.1" targetFramework="net45" /> <package id="Mono.Cecil" version="0.9.6.1" targetFramework="net45" />
<package id="Splat" version="1.6.2" targetFramework="net45" /> <package id="Splat" version="1.6.2" targetFramework="net45" />
<package id="squirrel.windows" version="1.5.2" targetFramework="net45" /> <package id="squirrel.windows" version="1.5.2" targetFramework="net45" />

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Modes.Catch.Objects;
using osu.Game.Modes.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Modes.Catch
{
public class CatchDifficultyCalculator : DifficultyCalculator<CatchBaseHit>
{
protected override PlayMode PlayMode => PlayMode.Catch;
public CatchDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
protected override HitObjectConverter<CatchBaseHit> Converter => new CatchConverter();
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty)
{
return 0;
}
}
}

View File

@ -1,14 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Modes.Catch.UI; using osu.Game.Modes.Catch.UI;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI; using osu.Game.Modes.Osu.UI;
using osu.Game.Modes.UI; using osu.Game.Modes.UI;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using System;
namespace osu.Game.Modes.Catch namespace osu.Game.Modes.Catch
{ {
@ -25,5 +24,7 @@ namespace osu.Game.Modes.Catch
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap);
} }
} }

View File

@ -47,6 +47,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="Objects\CatchBaseHit.cs" /> <Compile Include="Objects\CatchBaseHit.cs" />
<Compile Include="Objects\CatchConverter.cs" /> <Compile Include="Objects\CatchConverter.cs" />
<Compile Include="Objects\Drawable\DrawableFruit.cs" /> <Compile Include="Objects\Drawable\DrawableFruit.cs" />

View File

@ -0,0 +1,30 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Modes.Mania.Objects;
using osu.Game.Modes.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Modes.Mania
{
public class ManiaDifficultyCalculator : DifficultyCalculator<ManiaBaseHit>
{
protected override PlayMode PlayMode => PlayMode.Mania;
private int columns;
public ManiaDifficultyCalculator(Beatmap beatmap, int columns = 5) : base(beatmap)
{
this.columns = columns;
}
protected override HitObjectConverter<ManiaBaseHit> Converter => new ManiaConverter(columns);
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty)
{
return 0;
}
}
}

View File

@ -1,15 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Modes.Mania.UI; using osu.Game.Modes.Mania.UI;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI; using osu.Game.Modes.Osu.UI;
using osu.Game.Modes.UI; using osu.Game.Modes.UI;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using System;
namespace osu.Game.Modes.Mania namespace osu.Game.Modes.Mania
{ {
@ -26,5 +24,7 @@ namespace osu.Game.Modes.Mania
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap);
} }
} }

View File

@ -47,6 +47,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="ManiaDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableNote.cs" /> <Compile Include="Objects\Drawable\DrawableNote.cs" />
<Compile Include="Objects\HoldNote.cs" /> <Compile Include="Objects\HoldNote.cs" />
<Compile Include="Objects\ManiaBaseHit.cs" /> <Compile Include="Objects\ManiaBaseHit.cs" />

View File

@ -84,18 +84,18 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
double hitOffset = Math.Abs(Judgement.TimeOffset); double hitOffset = Math.Abs(Judgement.TimeOffset);
OsuJudgementInfo osuJudgement = Judgement as OsuJudgementInfo;
if (hitOffset < hit50) if (hitOffset < hit50)
{ {
Judgement.Result = HitResult.Hit; Judgement.Result = HitResult.Hit;
OsuJudgementInfo osuInfo = Judgement as OsuJudgementInfo;
if (hitOffset < hit300) if (hitOffset < hit300)
osuInfo.Score = OsuScoreResult.Hit300; osuJudgement.Score = OsuScoreResult.Hit300;
else if (hitOffset < hit100) else if (hitOffset < hit100)
osuInfo.Score = OsuScoreResult.Hit100; osuJudgement.Score = OsuScoreResult.Hit100;
else if (hitOffset < hit50) else if (hitOffset < hit50)
osuInfo.Score = OsuScoreResult.Hit50; osuJudgement.Score = OsuScoreResult.Hit50;
} }
else else
Judgement.Result = HitResult.Miss; Judgement.Result = HitResult.Miss;

View File

@ -4,7 +4,6 @@
using System.ComponentModel; using System.ComponentModel;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Objects.Drawables;
using osu.Framework.Graphics;
namespace osu.Game.Modes.Osu.Objects.Drawables namespace osu.Game.Modes.Osu.Objects.Drawables
{ {
@ -19,7 +18,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
{ {
} }
public override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo(); public override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo { MaxScore = OsuScoreResult.Hit300 };
protected override void UpdateState(ArmedState state) protected override void UpdateState(ArmedState state)
{ {
@ -49,7 +48,37 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
public class OsuJudgementInfo : PositionalJudgementInfo public class OsuJudgementInfo : PositionalJudgementInfo
{ {
/// <summary>
/// The score the user achieved.
/// </summary>
public OsuScoreResult Score; public OsuScoreResult Score;
/// <summary>
/// The score which would be achievable on a perfect hit.
/// </summary>
public OsuScoreResult MaxScore = OsuScoreResult.Hit300;
public int ScoreValue => scoreToInt(Score);
public int MaxScoreValue => scoreToInt(MaxScore);
private int scoreToInt(OsuScoreResult result)
{
switch (result)
{
default:
return 0;
case OsuScoreResult.Hit50:
return 50;
case OsuScoreResult.Hit100:
return 100;
case OsuScoreResult.Hit300:
return 300;
case OsuScoreResult.SliderTick:
return 10;
}
}
public ComboResult Combo; public ComboResult Combo;
} }
@ -73,5 +102,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Hit100, Hit100,
[Description(@"300")] [Description(@"300")]
Hit300, Hit300,
[Description(@"10")]
SliderTick
} }
} }

View File

@ -1,11 +1,13 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic; using OpenTK;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Game.Modes.Objects.Drawables; using osu.Game.Modes.Objects.Drawables;
using osu.Game.Modes.Osu.Objects.Drawables.Pieces; using osu.Game.Modes.Osu.Objects.Drawables.Pieces;
using OpenTK; using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Modes.Osu.Objects.Drawables namespace osu.Game.Modes.Osu.Objects.Drawables
{ {
@ -17,6 +19,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
private List<ISliderProgress> components = new List<ISliderProgress>(); private List<ISliderProgress> components = new List<ISliderProgress>();
private Container<DrawableSliderTick> ticks;
SliderBody body; SliderBody body;
SliderBall ball; SliderBall ball;
@ -33,6 +37,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Position = s.StackedPosition, Position = s.StackedPosition,
PathWidth = s.Scale * 64, PathWidth = s.Scale * 64,
}, },
ticks = new Container<DrawableSliderTick>(),
bouncer1 = new SliderBouncer(s, false) bouncer1 = new SliderBouncer(s, false)
{ {
Position = s.Curve.PositionAt(1), Position = s.Curve.PositionAt(1),
@ -63,6 +68,26 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
components.Add(ball); components.Add(ball);
components.Add(bouncer1); components.Add(bouncer1);
components.Add(bouncer2); components.Add(bouncer2);
AddNested(initialCircle);
var repeatDuration = s.Curve.Length / s.Velocity;
foreach (var tick in s.Ticks)
{
var repeatStartTime = s.StartTime + tick.RepeatIndex * repeatDuration;
var fadeInTime = repeatStartTime + (tick.StartTime - repeatStartTime) / 2 - (tick.RepeatIndex == 0 ? TIME_FADEIN : TIME_FADEIN / 2);
var fadeOutTime = repeatStartTime + repeatDuration;
var drawableTick = new DrawableSliderTick(tick)
{
FadeInTime = fadeInTime,
FadeOutTime = fadeOutTime,
Position = tick.Position,
};
ticks.Add(drawableTick);
AddNested(drawableTick);
}
} }
// Since the DrawableSlider itself is just a container without a size we need to // Since the DrawableSlider itself is just a container without a size we need to
@ -82,7 +107,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
if (repeat > currentRepeat) if (repeat > currentRepeat)
{ {
if (ball.Tracking) if (repeat < slider.RepeatCount && ball.Tracking)
PlaySample(); PlaySample();
currentRepeat = repeat; currentRepeat = repeat;
} }
@ -96,7 +121,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
if (initialCircle.Judgement?.Result != HitResult.Hit) if (initialCircle.Judgement?.Result != HitResult.Hit)
initialCircle.Position = slider.Curve.PositionAt(progress); initialCircle.Position = slider.Curve.PositionAt(progress);
components.ForEach(c => c.UpdateProgress(progress, repeat)); foreach (var c in components) c.UpdateProgress(progress, repeat);
foreach (var t in ticks.Children) t.Tracking = ball.Tracking;
} }
protected override void CheckJudgement(bool userTriggered) protected override void CheckJudgement(bool userTriggered)
@ -106,8 +132,22 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
if (!userTriggered && Time.Current >= HitObject.EndTime) if (!userTriggered && Time.Current >= HitObject.EndTime)
{ {
j.Score = sc.Score; var ticksCount = ticks.Children.Count() + 1;
j.Result = sc.Result; var ticksHit = ticks.Children.Count(t => t.Judgement.Result == HitResult.Hit);
if (sc.Result == HitResult.Hit)
ticksHit++;
var hitFraction = (double)ticksHit / ticksCount;
if (hitFraction == 1 && sc.Score == OsuScoreResult.Hit300)
j.Score = OsuScoreResult.Hit300;
else if (hitFraction >= 0.5 && sc.Score >= OsuScoreResult.Hit100)
j.Score = OsuScoreResult.Hit100;
else if (hitFraction > 0)
j.Score = OsuScoreResult.Hit50;
else
j.Score = OsuScoreResult.Miss;
j.Result = j.Score != OsuScoreResult.Miss ? HitResult.Hit : HitResult.Miss;
} }
} }

View File

@ -0,0 +1,120 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using osu.Game.Beatmaps.Samples;
using osu.Game.Modes.Objects.Drawables;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Modes.Osu.Objects.Drawables
{
public class DrawableSliderTick : DrawableOsuHitObject
{
private SliderTick sliderTick;
public double FadeInTime;
public double FadeOutTime;
public bool Tracking;
public override bool RemoveWhenNotAlive => false;
public override JudgementInfo CreateJudgementInfo() => new OsuJudgementInfo { MaxScore = OsuScoreResult.SliderTick };
public DrawableSliderTick(SliderTick sliderTick) : base(sliderTick)
{
this.sliderTick = sliderTick;
Size = new Vector2(16) * sliderTick.Scale;
Masking = true;
CornerRadius = Size.X / 2;
Origin = Anchor.Centre;
BorderThickness = 2;
BorderColour = Color4.White;
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = sliderTick.Colour,
Alpha = 0.3f,
}
};
}
private SampleChannel sample;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
string sampleSet = (HitObject.Sample?.Set ?? SampleSet.Normal).ToString().ToLower();
sample = audio.Sample.Get($@"Gameplay/{sampleSet}-slidertick");
}
protected override void PlaySample()
{
sample?.Play();
}
protected override void CheckJudgement(bool userTriggered)
{
var j = Judgement as OsuJudgementInfo;
if (Judgement.TimeOffset >= 0)
{
j.Result = Tracking ? HitResult.Hit : HitResult.Miss;
j.Score = Tracking ? OsuScoreResult.SliderTick : OsuScoreResult.Miss;
}
}
protected override void UpdatePreemptState()
{
var animIn = Math.Min(150, sliderTick.StartTime - FadeInTime);
ScaleTo(0.5f);
ScaleTo(1.2f, animIn);
FadeIn(animIn);
Delay(animIn);
ScaleTo(1, 150, EasingTypes.Out);
Delay(-animIn);
}
protected override void UpdateState(ArmedState state)
{
if (!IsLoaded) return;
base.UpdateState(state);
switch (state)
{
case ArmedState.Idle:
Delay(FadeOutTime - sliderTick.StartTime);
FadeOut();
break;
case ArmedState.Miss:
FadeOut(160);
FadeColour(Color4.Red, 80);
break;
case ArmedState.Hit:
FadeOut(120, EasingTypes.OutQuint);
ScaleTo(Scale * 1.5f, 120, EasingTypes.OutQuint);
break;
}
}
}
}

View File

@ -108,7 +108,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
} }
} }
private Vector2 scaleToCircle => new Vector2(circle.Scale * circle.DrawWidth / DrawWidth) * 0.95f; private Vector2 scaleToCircle => (circle.Scale * circle.DrawWidth / DrawWidth) * 0.95f;
private float spinsPerMinuteNeeded = 100 + (5 * 15); //TODO: read per-map OD and place it on the 5 private float spinsPerMinuteNeeded = 100 + (5 * 15); //TODO: read per-map OD and place it on the 5

View File

@ -25,7 +25,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces
{ {
BlendingMode = BlendingMode.Additive, BlendingMode = BlendingMode.Additive,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Alpha = 0.1f, Alpha = 0.2f,
} }
}; };
} }

View File

@ -8,7 +8,7 @@ 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.Graphics.OpenGL.Textures; using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites; using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Textures; using osu.Framework.Graphics.Textures;
using osu.Game.Configuration; using osu.Game.Configuration;
using OpenTK; using OpenTK;

View File

@ -1,9 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
namespace osu.Game.Modes.Osu.Objects namespace osu.Game.Modes.Osu.Objects
{ {
public class HitCircle : OsuHitObject public class HitCircle : OsuHitObject
{ {
public override HitObjectType Type => HitObjectType.Circle;
} }
} }

View File

@ -25,6 +25,8 @@ namespace osu.Game.Modes.Osu.Objects
public float Scale { get; set; } = 1; public float Scale { get; set; } = 1;
public abstract HitObjectType Type { get; }
public override void SetDefaultsFromBeatmap(Beatmap beatmap) public override void SetDefaultsFromBeatmap(Beatmap beatmap)
{ {
base.SetDefaultsFromBeatmap(beatmap); base.SetDefaultsFromBeatmap(beatmap);
@ -36,14 +38,12 @@ namespace osu.Game.Modes.Osu.Objects
[Flags] [Flags]
public enum HitObjectType public enum HitObjectType
{ {
Circle = 1, Circle = 1 << 0,
Slider = 2, Slider = 1 << 1,
NewCombo = 4, NewCombo = 1 << 2,
CircleNewCombo = 5, Spinner = 1 << 3,
SliderNewCombo = 6,
Spinner = 8,
ColourHax = 122, ColourHax = 122,
Hold = 128, Hold = 1 << 7,
ManiaLong = 128, SliderTick = 1 << 8,
} }
} }

View File

@ -0,0 +1,201 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using System;
using System.Diagnostics;
using System.Linq;
namespace osu.Game.Modes.Osu.Objects
{
class OsuHitObjectDifficulty
{
/// <summary>
/// Factor by how much speed / aim strain decays per second.
/// </summary>
/// <remarks>
/// These values are results of tweaking a lot and taking into account general feedback.
/// Opinionated observation: Speed is easier to maintain than accurate jumps.
/// </remarks>
internal static readonly double[] DECAY_BASE = { 0.3, 0.15 };
/// <summary>
/// Pseudo threshold values to distinguish between "singles" and "streams"
/// </summary>
/// <remarks>
/// Of course the border can not be defined clearly, therefore the algorithm has a smooth transition between those values.
/// They also are based on tweaking and general feedback.
/// </remarks>
private const double stream_spacing_threshold = 110,
single_spacing_threshold = 125;
/// <summary>
/// Scaling values for weightings to keep aim and speed difficulty in balance.
/// </summary>
/// <remarks>
/// Found from testing a very large map pool (containing all ranked maps) and keeping the average values the same.
/// </remarks>
private static readonly double[] spacing_weight_scaling = { 1400, 26.25 };
/// <summary>
/// Almost the normed diameter of a circle (104 osu pixel). That is -after- position transforming.
/// </summary>
private const double almost_diameter = 90;
internal OsuHitObject BaseHitObject;
internal double[] Strains = { 1, 1 };
internal int MaxCombo = 1;
private float scalingFactor;
private float lazySliderLength;
private Vector2 startPosition;
private Vector2 endPosition;
internal OsuHitObjectDifficulty(OsuHitObject baseHitObject)
{
BaseHitObject = baseHitObject;
float circleRadius = baseHitObject.Scale * 64;
Slider slider = BaseHitObject as Slider;
if (slider != null)
MaxCombo += slider.Ticks.Count();
// We will scale everything by this factor, so we can assume a uniform CircleSize among beatmaps.
scalingFactor = (52.0f / circleRadius);
if (circleRadius < 30)
{
float smallCircleBonus = Math.Min(30.0f - circleRadius, 5.0f) / 50.0f;
scalingFactor *= 1.0f + smallCircleBonus;
}
lazySliderLength = 0;
startPosition = baseHitObject.StackedPosition;
// Calculate approximation of lazy movement on the slider
if (slider != null)
{
float sliderFollowCircleRadius = circleRadius * 3; // Not sure if this is correct, but here we do not need 100% exact values. This comes pretty darn close in my tests.
// For simplifying this step we use actual osu! coordinates and simply scale the length, that we obtain by the ScalingFactor later
Vector2 cursorPos = startPosition;
Action<Vector2> addSliderVertex = delegate (Vector2 pos)
{
Vector2 difference = pos - cursorPos;
float distance = difference.Length;
// Did we move away too far?
if (distance > sliderFollowCircleRadius)
{
// Yep, we need to move the cursor
difference.Normalize(); // Obtain the direction of difference. We do no longer need the actual difference
distance -= sliderFollowCircleRadius;
cursorPos += difference * distance; // We move the cursor just as far as needed to stay in the follow circle
lazySliderLength += distance;
}
};
// Actual computation of the first lazy curve
foreach (var tick in slider.Ticks)
addSliderVertex(tick.StackedPosition);
addSliderVertex(baseHitObject.StackedEndPosition);
lazySliderLength *= scalingFactor;
endPosition = cursorPos;
}
// We have a normal HitCircle or a spinner
else
endPosition = startPosition;
}
internal void CalculateStrains(OsuHitObjectDifficulty previousHitObject, double timeRate)
{
calculateSpecificStrain(previousHitObject, OsuDifficultyCalculator.DifficultyType.Speed, timeRate);
calculateSpecificStrain(previousHitObject, OsuDifficultyCalculator.DifficultyType.Aim, timeRate);
}
// Caution: The subjective values are strong with this one
private static double spacingWeight(double distance, OsuDifficultyCalculator.DifficultyType type)
{
switch (type)
{
case OsuDifficultyCalculator.DifficultyType.Speed:
if (distance > single_spacing_threshold)
return 2.5;
else if (distance > stream_spacing_threshold)
return 1.6 + 0.9 * (distance - stream_spacing_threshold) / (single_spacing_threshold - stream_spacing_threshold);
else if (distance > almost_diameter)
return 1.2 + 0.4 * (distance - almost_diameter) / (stream_spacing_threshold - almost_diameter);
else if (distance > almost_diameter / 2)
return 0.95 + 0.25 * (distance - (almost_diameter / 2)) / (almost_diameter / 2);
else
return 0.95;
case OsuDifficultyCalculator.DifficultyType.Aim:
return Math.Pow(distance, 0.99);
}
Debug.Assert(false, "Invalid osu difficulty hit object type.");
return 0;
}
private void calculateSpecificStrain(OsuHitObjectDifficulty previousHitObject, OsuDifficultyCalculator.DifficultyType type, double timeRate)
{
double addition = 0;
double timeElapsed = (BaseHitObject.StartTime - previousHitObject.BaseHitObject.StartTime) / timeRate;
double decay = Math.Pow(DECAY_BASE[(int)type], timeElapsed / 1000);
if (BaseHitObject.Type == HitObjectType.Spinner)
{
// Do nothing for spinners
}
else if (BaseHitObject.Type == HitObjectType.Slider)
{
switch (type)
{
case OsuDifficultyCalculator.DifficultyType.Speed:
// For speed strain we treat the whole slider as a single spacing entity, since "Speed" is about how hard it is to click buttons fast.
// The spacing weight exists to differentiate between being able to easily alternate or having to single.
addition =
spacingWeight(previousHitObject.lazySliderLength +
DistanceTo(previousHitObject), type) *
spacing_weight_scaling[(int)type];
break;
case OsuDifficultyCalculator.DifficultyType.Aim:
// For Aim strain we treat each slider segment and the jump after the end of the slider as separate jumps, since movement-wise there is no difference
// to multiple jumps.
addition =
(
spacingWeight(previousHitObject.lazySliderLength, type) +
spacingWeight(DistanceTo(previousHitObject), type)
) *
spacing_weight_scaling[(int)type];
break;
}
}
else if (BaseHitObject.Type == HitObjectType.Circle)
{
addition = spacingWeight(DistanceTo(previousHitObject), type) * spacing_weight_scaling[(int)type];
}
// Scale addition by the time, that elapsed. Filter out HitObjects that are too close to be played anyway to avoid crazy values by division through close to zero.
// You will never find maps that require this amongst ranked maps.
addition /= Math.Max(timeElapsed, 50);
Strains[(int)type] = previousHitObject.Strains[(int)type] * decay + addition;
}
internal double DistanceTo(OsuHitObjectDifficulty other)
{
// Scale the distance by circle size.
return (startPosition - other.endPosition).Length * scalingFactor;
}
}
}

View File

@ -1,9 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
using OpenTK; using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Samples;
using osu.Game.Beatmaps.Timing;
using System;
using System.Collections.Generic;
namespace osu.Game.Modes.Osu.Objects namespace osu.Game.Modes.Osu.Objects
{ {
@ -43,17 +46,72 @@ namespace osu.Game.Modes.Osu.Objects
} }
public double Velocity; public double Velocity;
public double TickDistance;
public override void SetDefaultsFromBeatmap(Beatmap beatmap) public override void SetDefaultsFromBeatmap(Beatmap beatmap)
{ {
base.SetDefaultsFromBeatmap(beatmap); base.SetDefaultsFromBeatmap(beatmap);
Velocity = 100 / beatmap.BeatLengthAt(StartTime, true) * beatmap.BeatmapInfo.BaseDifficulty.SliderMultiplier; var baseDifficulty = beatmap.BeatmapInfo.BaseDifficulty;
ControlPoint overridePoint;
ControlPoint timingPoint = beatmap.TimingPointAt(StartTime, out overridePoint);
var velocityAdjustment = overridePoint?.VelocityAdjustment ?? 1;
var baseVelocity = 100 * baseDifficulty.SliderMultiplier / velocityAdjustment;
Velocity = baseVelocity / timingPoint.BeatLength;
TickDistance = baseVelocity / baseDifficulty.SliderTickRate;
} }
public int RepeatCount = 1; public int RepeatCount = 1;
internal readonly SliderCurve Curve = new SliderCurve(); internal readonly SliderCurve Curve = new SliderCurve();
public IEnumerable<SliderTick> Ticks
{
get
{
if (TickDistance == 0) yield break;
var length = Curve.Length;
var tickDistance = Math.Min(TickDistance, length);
var repeatDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01;
for (var repeat = 0; repeat < RepeatCount; repeat++)
{
var repeatStartTime = StartTime + repeat * repeatDuration;
var reversed = repeat % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance)
{
if (d > length - minDistanceFromEnd)
break;
var distanceProgress = d / length;
var timeProgress = reversed ? 1 - distanceProgress : distanceProgress;
yield return new SliderTick
{
RepeatIndex = repeat,
StartTime = repeatStartTime + timeProgress * repeatDuration,
Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
Colour = Colour,
Sample = new HitSampleInfo
{
Type = SampleType.None,
Set = SampleSet.Soft,
},
};
}
}
}
}
public override HitObjectType Type => HitObjectType.Slider;
} }
public enum CurveTypes public enum CurveTypes

View File

@ -0,0 +1,11 @@
using OpenTK;
namespace osu.Game.Modes.Osu.Objects
{
public class SliderTick : OsuHitObject
{
public int RepeatIndex { get; set; }
public override HitObjectType Type => HitObjectType.SliderTick;
}
}

View File

@ -10,5 +10,7 @@ namespace osu.Game.Modes.Osu.Objects
public double Length; public double Length;
public override double EndTime => StartTime + Length; public override double EndTime => StartTime + Length;
public override HitObjectType Type => HitObjectType.Spinner;
} }
} }

View File

@ -0,0 +1,193 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Modes.Osu.Objects;
using System;
using System.Collections.Generic;
using osu.Game.Modes.Objects;
namespace osu.Game.Modes.Osu
{
public class OsuDifficultyCalculator : DifficultyCalculator<OsuHitObject>
{
private const double star_scaling_factor = 0.0675;
private const double extreme_scaling_factor = 0.5;
protected override PlayMode PlayMode => PlayMode.Osu;
/// <summary>
/// HitObjects are stored as a member variable.
/// </summary>
internal List<OsuHitObjectDifficulty> DifficultyHitObjects = new List<OsuHitObjectDifficulty>();
public OsuDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
protected override HitObjectConverter<OsuHitObject> Converter => new OsuHitObjectConverter();
protected override void PreprocessHitObjects()
{
foreach (var h in Objects)
if (h.Type == HitObjectType.Slider)
((Slider)h).Curve.Calculate();
}
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty)
{
// Fill our custom DifficultyHitObject class, that carries additional information
DifficultyHitObjects.Clear();
foreach (var hitObject in Objects)
DifficultyHitObjects.Add(new OsuHitObjectDifficulty(hitObject));
// Sort DifficultyHitObjects by StartTime of the HitObjects - just to make sure.
DifficultyHitObjects.Sort((a, b) => a.BaseHitObject.StartTime.CompareTo(b.BaseHitObject.StartTime));
if (!CalculateStrainValues()) return 0;
double speedDifficulty = CalculateDifficulty(DifficultyType.Speed);
double aimDifficulty = CalculateDifficulty(DifficultyType.Aim);
// OverallDifficulty is not considered in this algorithm and neither is HpDrainRate. That means, that in this form the algorithm determines how hard it physically is
// to play the map, assuming, that too much of an error will not lead to a death.
// It might be desirable to include OverallDifficulty into map difficulty, but in my personal opinion it belongs more to the weighting of the actual peformance
// and is superfluous in the beatmap difficulty rating.
// If it were to be considered, then I would look at the hit window of normal HitCircles only, since Sliders and Spinners are (almost) "free" 300s and take map length
// into account as well.
// The difficulty can be scaled by any desired metric.
// In osu!tp it gets squared to account for the rapid increase in difficulty as the limit of a human is approached. (Of course it also gets scaled afterwards.)
// It would not be suitable for a star rating, therefore:
// The following is a proposal to forge a star rating from 0 to 5. It consists of taking the square root of the difficulty, since by simply scaling the easier
// 5-star maps would end up with one star.
double speedStars = Math.Sqrt(speedDifficulty) * star_scaling_factor;
double aimStars = Math.Sqrt(aimDifficulty) * star_scaling_factor;
if (categoryDifficulty != null)
{
categoryDifficulty.Add("Aim", aimStars.ToString("0.00"));
categoryDifficulty.Add("Speed", speedStars.ToString("0.00"));
double hitWindow300 = 30/*HitObjectManager.HitWindow300*/ / TimeRate;
double preEmpt = 450/*HitObjectManager.PreEmpt*/ / TimeRate;
categoryDifficulty.Add("OD", (-(hitWindow300 - 80.0) / 6.0).ToString("0.00"));
categoryDifficulty.Add("AR", (preEmpt > 1200.0 ? -(preEmpt - 1800.0) / 120.0 : -(preEmpt - 1200.0) / 150.0 + 5.0).ToString("0.00"));
int maxCombo = 0;
foreach (OsuHitObjectDifficulty hitObject in DifficultyHitObjects)
maxCombo += hitObject.MaxCombo;
categoryDifficulty.Add("Max combo", maxCombo.ToString());
}
// Again, from own observations and from the general opinion of the community a map with high speed and low aim (or vice versa) difficulty is harder,
// than a map with mediocre difficulty in both. Therefore we can not just add both difficulties together, but will introduce a scaling that favors extremes.
double starRating = speedStars + aimStars + Math.Abs(speedStars - aimStars) * extreme_scaling_factor;
// Another approach to this would be taking Speed and Aim separately to a chosen power, which again would be equivalent. This would be more convenient if
// the hit window size is to be considered as well.
// Note: The star rating is tuned extremely tight! Airman (/b/104229) and Freedom Dive (/b/126645), two of the hardest ranked maps, both score ~4.66 stars.
// Expect the easier kind of maps that officially get 5 stars to obtain around 2 by this metric. The tutorial still scores about half a star.
// Tune by yourself as you please. ;)
return starRating;
}
protected bool CalculateStrainValues()
{
// Traverse hitObjects in pairs to calculate the strain value of NextHitObject from the strain value of CurrentHitObject and environment.
List<OsuHitObjectDifficulty>.Enumerator hitObjectsEnumerator = DifficultyHitObjects.GetEnumerator();
if (!hitObjectsEnumerator.MoveNext()) return false;
OsuHitObjectDifficulty currentHitObject = hitObjectsEnumerator.Current;
OsuHitObjectDifficulty nextHitObject;
// First hitObject starts at strain 1. 1 is the default for strain values, so we don't need to set it here. See DifficultyHitObject.
while (hitObjectsEnumerator.MoveNext())
{
nextHitObject = hitObjectsEnumerator.Current;
nextHitObject.CalculateStrains(currentHitObject, TimeRate);
currentHitObject = nextHitObject;
}
return true;
}
/// <summary>
/// In milliseconds. For difficulty calculation we will only look at the highest strain value in each time interval of size STRAIN_STEP.
/// This is to eliminate higher influence of stream over aim by simply having more HitObjects with high strain.
/// The higher this value, the less strains there will be, indirectly giving long beatmaps an advantage.
/// </summary>
protected const double STRAIN_STEP = 400;
/// <summary>
/// The weighting of each strain value decays to this number * it's previous value
/// </summary>
protected const double DECAY_WEIGHT = 0.9;
protected double CalculateDifficulty(DifficultyType type)
{
double actualStrainStep = STRAIN_STEP * TimeRate;
// Find the highest strain value within each strain step
List<double> highestStrains = new List<double>();
double intervalEndTime = actualStrainStep;
double maximumStrain = 0; // We need to keep track of the maximum strain in the current interval
OsuHitObjectDifficulty previousHitObject = null;
foreach (OsuHitObjectDifficulty hitObject in DifficultyHitObjects)
{
// While we are beyond the current interval push the currently available maximum to our strain list
while (hitObject.BaseHitObject.StartTime > intervalEndTime)
{
highestStrains.Add(maximumStrain);
// The maximum strain of the next interval is not zero by default! We need to take the last hitObject we encountered, take its strain and apply the decay
// until the beginning of the next interval.
if (previousHitObject == null)
{
maximumStrain = 0;
}
else
{
double decay = Math.Pow(OsuHitObjectDifficulty.DECAY_BASE[(int)type], (intervalEndTime - previousHitObject.BaseHitObject.StartTime) / 1000);
maximumStrain = previousHitObject.Strains[(int)type] * decay;
}
// Go to the next time interval
intervalEndTime += actualStrainStep;
}
// Obtain maximum strain
maximumStrain = Math.Max(hitObject.Strains[(int)type], maximumStrain);
previousHitObject = hitObject;
}
// Build the weighted sum over the highest strains for each interval
double difficulty = 0;
double weight = 1;
highestStrains.Sort((a, b) => b.CompareTo(a)); // Sort from highest to lowest strain.
foreach (double strain in highestStrains)
{
difficulty += weight * strain;
weight *= DECAY_WEIGHT;
}
return difficulty;
}
// Those values are used as array indices. Be careful when changing them!
public enum DifficultyType : int
{
Speed = 0,
Aim,
};
}
}

View File

@ -40,6 +40,8 @@ namespace osu.Game.Modes.Osu
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => new OsuScoreProcessor(hitObjectCount); public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => new OsuScoreProcessor(hitObjectCount);
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new OsuDifficultyCalculator(beatmap);
protected override PlayMode PlayMode => PlayMode.Osu; protected override PlayMode PlayMode => PlayMode.Osu;
} }
} }

View File

@ -36,24 +36,8 @@ namespace osu.Game.Modes.Osu
foreach (OsuJudgementInfo j in Judgements) foreach (OsuJudgementInfo j in Judgements)
{ {
switch (j.Score) score += j.ScoreValue;
{ maxScore += j.MaxScoreValue;
case OsuScoreResult.Miss:
maxScore += 300;
break;
case OsuScoreResult.Hit50:
score += 50;
maxScore += 300;
break;
case OsuScoreResult.Hit100:
score += 100;
maxScore += 300;
break;
case OsuScoreResult.Hit300:
score += 300;
maxScore += 300;
break;
}
} }
TotalScore.Value = score; TotalScore.Value = score;

View File

@ -13,19 +13,21 @@ namespace osu.Game.Modes.Osu.UI
{ {
public class OsuScoreOverlay : ScoreOverlay public class OsuScoreOverlay : ScoreOverlay
{ {
protected override ScoreCounter CreateScoreCounter() => new ScoreCounter() protected override ScoreCounter CreateScoreCounter() => new ScoreCounter(6)
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopCentre,
Origin = Anchor.TopRight, Origin = Anchor.TopCentre,
TextSize = 60, TextSize = 40,
Position = new Vector2(0, 30),
Margin = new MarginPadding { Right = 5 }, Margin = new MarginPadding { Right = 5 },
}; };
protected override PercentageCounter CreateAccuracyCounter() => new PercentageCounter() protected override PercentageCounter CreateAccuracyCounter() => new PercentageCounter()
{ {
Anchor = Anchor.TopRight, Anchor = Anchor.TopCentre,
Origin = Anchor.TopRight, Origin = Anchor.TopCentre,
Position = new Vector2(0, 55), Position = new Vector2(0, 65),
TextSize = 20,
Margin = new MarginPadding { Right = 5 }, Margin = new MarginPadding { Right = 5 },
}; };

View File

@ -59,14 +59,18 @@
<Compile Include="Objects\Drawables\Pieces\GlowPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\GlowPiece.cs" />
<Compile Include="Objects\Drawables\HitExplosion.cs" /> <Compile Include="Objects\Drawables\HitExplosion.cs" />
<Compile Include="Objects\Drawables\Pieces\NumberPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\NumberPiece.cs" />
<Compile Include="Objects\Drawables\DrawableSliderTick.cs" />
<Compile Include="Objects\Drawables\Pieces\RingPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\RingPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBouncer.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBouncer.cs" />
<Compile Include="Objects\Drawables\Pieces\SpinnerDisc.cs" /> <Compile Include="Objects\Drawables\Pieces\SpinnerDisc.cs" />
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" /> <Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" /> <Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\OsuHitObjectDifficulty.cs" />
<Compile Include="Objects\OsuHitObjectParser.cs" /> <Compile Include="Objects\OsuHitObjectParser.cs" />
<Compile Include="Objects\SliderCurve.cs" /> <Compile Include="Objects\SliderCurve.cs" />
<Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuDifficultyCalculator.cs" />
<Compile Include="OsuScore.cs" /> <Compile Include="OsuScore.cs" />
<Compile Include="OsuScoreProcessor.cs" /> <Compile Include="OsuScoreProcessor.cs" />
<Compile Include="UI\OsuComboCounter.cs" /> <Compile Include="UI\OsuComboCounter.cs" />

View File

@ -0,0 +1,27 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Beatmaps;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Taiko.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Modes.Taiko
{
public class TaikoDifficultyCalculator : DifficultyCalculator<TaikoBaseHit>
{
protected override PlayMode PlayMode => PlayMode.Taiko;
public TaikoDifficultyCalculator(Beatmap beatmap) : base(beatmap)
{
}
protected override HitObjectConverter<TaikoBaseHit> Converter => new TaikoConverter();
protected override double ComputeDifficulty(Dictionary<String, String> categoryDifficulty)
{
return 0;
}
}
}

View File

@ -2,10 +2,8 @@
// 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 osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Modes.Objects; using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI; using osu.Game.Modes.Osu.UI;
using osu.Game.Modes.Taiko.UI; using osu.Game.Modes.Taiko.UI;
using osu.Game.Modes.UI; using osu.Game.Modes.UI;
@ -26,5 +24,7 @@ namespace osu.Game.Modes.Taiko
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null; public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser(); public override HitObjectParser CreateHitObjectParser() => new NullHitObjectParser();
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap);
} }
} }

View File

@ -47,6 +47,7 @@
<Reference Include="System.Xml" /> <Reference Include="System.Xml" />
</ItemGroup> </ItemGroup>
<ItemGroup> <ItemGroup>
<Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableTaikoHit.cs" /> <Compile Include="Objects\Drawable\DrawableTaikoHit.cs" />
<Compile Include="Objects\TaikoBaseHit.cs" /> <Compile Include="Objects\TaikoBaseHit.cs" />
<Compile Include="Objects\TaikoConverter.cs" /> <Compile Include="Objects\TaikoConverter.cs" />

View File

@ -1,18 +1,15 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.IO; using System.IO;
using NUnit.Framework; using NUnit.Framework;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.Formats; using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Samples; using osu.Game.Beatmaps.Samples;
using osu.Game.Modes; using osu.Game.Modes;
using osu.Game.Modes.Osu; using osu.Game.Modes.Osu;
using osu.Game.Modes.Osu.Objects; using osu.Game.Modes.Osu.Objects;
using osu.Game.Screens.Play;
using osu.Game.Tests.Resources; using osu.Game.Tests.Resources;
namespace osu.Game.Tests.Beatmaps.Formats namespace osu.Game.Tests.Beatmaps.Formats

View File

@ -26,26 +26,36 @@ namespace osu.Game.Beatmaps
return 60000 / BeatLengthAt(time); return 60000 / BeatLengthAt(time);
} }
public double BeatLengthAt(double time, bool applyMultipliers = false) public double BeatLengthAt(double time)
{ {
int point = 0; ControlPoint overridePoint;
int samplePoint = 0; ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
return timingPoint.BeatLength;
}
for (int i = 0; i < ControlPoints.Count; i++) public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint)
if (ControlPoints[i].Time <= time) {
overridePoint = null;
ControlPoint timingPoint = null;
foreach (var controlPoint in ControlPoints)
{
// Some beatmaps have the first timingPoint (accidentally) start after the first HitObject(s).
// This null check makes it so that the first ControlPoint that makes a timing change is used as
// the timingPoint for those HitObject(s).
if (controlPoint.Time <= time || timingPoint == null)
{ {
if (ControlPoints[i].TimingChange) if (controlPoint.TimingChange)
point = i; {
else timingPoint = controlPoint;
samplePoint = i; overridePoint = null;
}
else overridePoint = controlPoint;
} }
else break;
}
double mult = 1; return timingPoint ?? ControlPoint.Default;
if (applyMultipliers && samplePoint > point)
mult = ControlPoints[samplePoint].VelocityAdjustment;
return ControlPoints[point].BeatLength * mult;
} }
} }
} }

View File

@ -0,0 +1,50 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Modes;
using osu.Game.Modes.Objects;
using System;
using System.Collections.Generic;
namespace osu.Game.Beatmaps
{
public abstract class DifficultyCalculator
{
protected abstract PlayMode PlayMode { get; }
protected double TimeRate = 1;
protected abstract double ComputeDifficulty(Dictionary<String, String> categoryDifficulty);
private void loadTiming()
{
// TODO: Handle mods
int audioRate = 100;
TimeRate = audioRate / 100.0;
}
public double GetDifficulty(Dictionary<string, string> categoryDifficulty = null)
{
loadTiming();
double difficulty = ComputeDifficulty(categoryDifficulty);
return difficulty;
}
}
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
{
protected List<T> Objects;
protected abstract HitObjectConverter<T> Converter { get; }
public DifficultyCalculator(Beatmap beatmap)
{
Objects = Converter.Convert(beatmap);
PreprocessHitObjects();
}
protected virtual void PreprocessHitObjects()
{
}
}
}

View File

@ -60,7 +60,7 @@ namespace osu.Game.Beatmaps.Drawables
} }
} }
public BeatmapGroup(WorkingBeatmap beatmap, BeatmapSetInfo set = null) public BeatmapGroup(WorkingBeatmap beatmap)
{ {
Header = new BeatmapSetHeader(beatmap) Header = new BeatmapSetHeader(beatmap)
{ {
@ -68,6 +68,7 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}; };
BeatmapSet = beatmap.BeatmapSetInfo;
BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b) BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b)
{ {
Alpha = 0, Alpha = 0,
@ -76,7 +77,6 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
}).ToList(); }).ToList();
BeatmapSet = set;
Header.AddDifficultyIcons(BeatmapPanels); Header.AddDifficultyIcons(BeatmapPanels);
} }

View File

@ -1,20 +1,21 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Game.Beatmaps.Timing namespace osu.Game.Beatmaps.Timing
{ {
public class ControlPoint public class ControlPoint
{ {
public static ControlPoint Default = new ControlPoint
{
BeatLength = 500,
TimingChange = true,
};
public double Time; public double Time;
public double BeatLength; public double BeatLength;
public double VelocityAdjustment; public double VelocityAdjustment;
public bool TimingChange; public bool TimingChange;
} }
internal enum TimeSignatures internal enum TimeSignatures

View File

@ -1,12 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
namespace osu.Game.Beatmaps.Timing namespace osu.Game.Beatmaps.Timing
{ {
class TimingChange : ControlPoint class TimingChange : ControlPoint

View File

@ -68,7 +68,6 @@ namespace osu.Game.Beatmaps
beatmap = decoder?.Decode(stream); beatmap = decoder?.Decode(stream);
} }
if (WithStoryboard && beatmap != null && BeatmapSetInfo.StoryboardFile != null) if (WithStoryboard && beatmap != null && BeatmapSetInfo.StoryboardFile != null)
using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile))) using (var stream = new StreamReader(reader.GetStream(BeatmapSetInfo.StoryboardFile)))
decoder?.Decode(stream, beatmap); decoder?.Decode(stream, beatmap);
@ -83,9 +82,9 @@ namespace osu.Game.Beatmaps
} }
private ArchiveReader trackReader; private ArchiveReader trackReader;
private AudioTrack track; private Track track;
private object trackLock = new object(); private object trackLock = new object();
public AudioTrack Track public Track Track
{ {
get get
{ {
@ -99,7 +98,7 @@ namespace osu.Game.Beatmaps
trackReader = getReader(); trackReader = getReader();
var trackData = trackReader?.GetStream(BeatmapInfo.Metadata.AudioFile); var trackData = trackReader?.GetStream(BeatmapInfo.Metadata.AudioFile);
if (trackData != null) if (trackData != null)
track = new AudioTrackBass(trackData); track = new TrackBass(trackData);
} }
catch { } catch { }

View File

@ -31,6 +31,7 @@ namespace osu.Game.Configuration
Set(OsuConfig.SnakingInSliders, true); Set(OsuConfig.SnakingInSliders, true);
Set(OsuConfig.SnakingOutSliders, false); Set(OsuConfig.SnakingOutSliders, false);
Set(OsuConfig.MenuParallax, true);
//todo: implement all settings below this line (remove the Disabled set when doing so). //todo: implement all settings below this line (remove the Disabled set when doing so).
Set(OsuConfig.MouseSpeed, 1.0).Disabled = true; Set(OsuConfig.MouseSpeed, 1.0).Disabled = true;
@ -143,7 +144,6 @@ namespace osu.Game.Configuration
Set(OsuConfig.DetectPerformanceIssues, true).Disabled = true; Set(OsuConfig.DetectPerformanceIssues, true).Disabled = true;
Set(OsuConfig.MenuMusic, true).Disabled = true; Set(OsuConfig.MenuMusic, true).Disabled = true;
Set(OsuConfig.MenuVoice, true).Disabled = true; Set(OsuConfig.MenuVoice, true).Disabled = true;
Set(OsuConfig.MenuParallax, true).Disabled = true;
Set(OsuConfig.RawInput, false).Disabled = true; Set(OsuConfig.RawInput, false).Disabled = true;
Set(OsuConfig.AbsoluteToOsuWindow, Get<bool>(OsuConfig.RawInput)).Disabled = true; Set(OsuConfig.AbsoluteToOsuWindow, Get<bool>(OsuConfig.RawInput)).Disabled = true;
Set(OsuConfig.ShowMenuTips, true).Disabled = true; Set(OsuConfig.ShowMenuTips, true).Disabled = true;

View File

@ -8,6 +8,7 @@ using osu.Game.Modes;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using SQLite.Net.Attributes; using SQLite.Net.Attributes;
using SQLiteNetExtensions.Attributes; using SQLiteNetExtensions.Attributes;
using osu.Game.Beatmaps;
namespace osu.Game.Database namespace osu.Game.Database
{ {
@ -73,7 +74,23 @@ namespace osu.Game.Database
// Metadata // Metadata
public string Version { get; set; } public string Version { get; set; }
public float StarDifficulty => BaseDifficulty?.OverallDifficulty ?? 5; //todo: implement properly //todo: background threaded computation of this
private float starDifficulty = -1;
public float StarDifficulty
{
get
{
return (starDifficulty < 0) ? (BaseDifficulty?.OverallDifficulty ?? 5) : starDifficulty;
}
set { starDifficulty = value; }
}
internal void ComputeDifficulty(BeatmapDatabase database)
{
WorkingBeatmap wb = new WorkingBeatmap(this, BeatmapSet, database);
StarDifficulty = (float)Ruleset.GetRuleset(Mode).CreateDifficultyCalculator(wb.Beatmap).GetDifficulty();
}
public bool Equals(BeatmapInfo other) public bool Equals(BeatmapInfo other)
{ {

View File

@ -8,6 +8,8 @@ using OpenTK;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Graphics.Transformations; using osu.Framework.Graphics.Transformations;
using osu.Game.Configuration;
using osu.Framework.Configuration;
namespace osu.Game.Graphics.Containers namespace osu.Game.Graphics.Containers
{ {
@ -15,6 +17,8 @@ namespace osu.Game.Graphics.Containers
{ {
public float ParallaxAmount = 0.02f; public float ParallaxAmount = 0.02f;
private Bindable<bool> parallaxEnabled;
public override bool Contains(Vector2 screenSpacePos) => true; public override bool Contains(Vector2 screenSpacePos) => true;
public ParallaxContainer() public ParallaxContainer()
@ -34,9 +38,18 @@ namespace osu.Game.Graphics.Containers
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(UserInputManager input) private void load(UserInputManager input, OsuConfigManager config)
{ {
this.input = input; this.input = input;
parallaxEnabled = config.GetBindable<bool>(OsuConfig.MenuParallax);
parallaxEnabled.ValueChanged += delegate
{
if (!parallaxEnabled)
{
content.MoveTo(Vector2.Zero, firstUpdate ? 0 : 1000, EasingTypes.OutQuint);
content.Scale = new Vector2(1 + ParallaxAmount);
}
};
} }
bool firstUpdate = true; bool firstUpdate = true;
@ -45,9 +58,12 @@ namespace osu.Game.Graphics.Containers
{ {
base.Update(); base.Update();
Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2; if (parallaxEnabled)
content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint); {
content.Scale = new Vector2(1 + ParallaxAmount); Vector2 offset = input.CurrentState.Mouse == null ? Vector2.Zero : ToLocalSpace(input.CurrentState.Mouse.NativeState.Position) - DrawSize / 2;
content.MoveTo(offset * ParallaxAmount, firstUpdate ? 0 : 1000, EasingTypes.OutQuint);
content.Scale = new Vector2(1 + ParallaxAmount);
}
firstUpdate = false; firstUpdate = false;
} }

View File

@ -0,0 +1,49 @@
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Input;
using System;
using System.Linq;
namespace osu.Game.Graphics.UserInterface
{
public class FocusedTextBox : OsuTextBox
{
protected override Color4 BackgroundUnfocused => new Color4(10, 10, 10, 255);
protected override Color4 BackgroundFocused => new Color4(10, 10, 10, 255);
public Action Exit;
private bool focus;
public bool HoldFocus
{
get { return focus; }
set
{
focus = value;
if (!focus)
TriggerFocusLost();
}
}
protected override bool OnFocus(InputState state)
{
var result = base.OnFocus(state);
BorderThickness = 0;
return result;
}
protected override void OnFocusLost(InputState state)
{
if (state.Keyboard.Keys.Any(key => key == Key.Escape))
{
if (Text.Length > 0)
Text = string.Empty;
else
Exit?.Invoke();
}
base.OnFocusLost(state);
}
public override bool RequestingFocus => HoldFocus;
}
}

View File

@ -71,8 +71,8 @@ namespace osu.Game.Graphics.UserInterface
private Nub nub; private Nub nub;
private SpriteText labelSpriteText; private SpriteText labelSpriteText;
private AudioSample sampleChecked; private SampleChannel sampleChecked;
private AudioSample sampleUnchecked; private SampleChannel sampleUnchecked;
public OsuCheckbox() public OsuCheckbox()
{ {

View File

@ -16,7 +16,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
public class OsuSliderBar<U> : SliderBar<U> where U : struct public class OsuSliderBar<U> : SliderBar<U> where U : struct
{ {
private AudioSample sample; private SampleChannel sample;
private double lastSampleTime; private double lastSampleTime;
private Nub nub; private Nub nub;

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface; using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Graphics.UserInterface namespace osu.Game.Graphics.UserInterface

View File

@ -20,8 +20,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
protected override Type TransformType => typeof(TransformAccuracy); protected override Type TransformType => typeof(TransformAccuracy);
protected override double RollingDuration => 150; protected override double RollingDuration => 750;
protected override bool IsRollingProportional => true;
private float epsilon => 1e-10f; private float epsilon => 1e-10f;
@ -32,6 +31,7 @@ namespace osu.Game.Graphics.UserInterface
public PercentageCounter() public PercentageCounter()
{ {
DisplayedCountSpriteText.FixedWidth = true;
Count = 1.0f; Count = 1.0f;
} }

View File

@ -38,7 +38,7 @@ namespace osu.Game.Graphics.UserInterface
/// <summary> /// <summary>
/// Easing for the counter rollover animation. /// Easing for the counter rollover animation.
/// </summary> /// </summary>
protected virtual EasingTypes RollingEasing => EasingTypes.Out; protected virtual EasingTypes RollingEasing => EasingTypes.OutQuint;
private T displayedCount; private T displayedCount;
@ -107,17 +107,16 @@ namespace osu.Game.Graphics.UserInterface
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
DisplayedCountSpriteText = new OsuSpriteText(), DisplayedCountSpriteText = new OsuSpriteText()
{
Font = @"Venera"
},
}; };
TextSize = 40; TextSize = 40;
AutoSizeAxes = Axes.Both; AutoSizeAxes = Axes.Both;
DisplayedCount = Count; DisplayedCount = Count;
DisplayedCountSpriteText.Text = FormatCount(count);
DisplayedCountSpriteText.Anchor = Anchor;
DisplayedCountSpriteText.Origin = Origin;
} }
protected override void LoadComplete() protected override void LoadComplete()
@ -125,6 +124,10 @@ namespace osu.Game.Graphics.UserInterface
base.LoadComplete(); base.LoadComplete();
Flush(false, TransformType); Flush(false, TransformType);
DisplayedCountSpriteText.Text = FormatCount(count);
DisplayedCountSpriteText.Anchor = Anchor;
DisplayedCountSpriteText.Origin = Origin;
} }
/// <summary> /// <summary>

View File

@ -28,7 +28,7 @@ namespace osu.Game.Graphics.UserInterface
public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50); public static readonly Vector2 SIZE_EXTENDED = new Vector2(140, 50);
public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50); public static readonly Vector2 SIZE_RETRACTED = new Vector2(100, 50);
public AudioSample ActivationSound; public SampleChannel ActivationSound;
private SpriteText text; private SpriteText text;
public Color4 HoverColour; public Color4 HoverColour;

View File

@ -13,6 +13,11 @@ namespace osu.Game.Input
public override bool HandleInput => true; public override bool HandleInput => true;
public GlobalHotkeys()
{
RelativeSizeAxes = Axes.Both;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{ {
return Handler(state, args); return Handler(state, args);

View File

@ -2,6 +2,7 @@
// 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.ComponentModel; using System.ComponentModel;
using System.Diagnostics; using System.Diagnostics;
using osu.Framework; using osu.Framework;
@ -23,8 +24,6 @@ namespace osu.Game.Modes.Objects.Drawables
public bool Interactive = true; public bool Interactive = true;
public Container<DrawableHitObject> ChildObjects;
public JudgementInfo Judgement; public JudgementInfo Judgement;
public abstract JudgementInfo CreateJudgementInfo(); public abstract JudgementInfo CreateJudgementInfo();
@ -55,7 +54,7 @@ namespace osu.Game.Modes.Objects.Drawables
} }
} }
AudioSample sample; SampleChannel sample;
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
@ -66,7 +65,7 @@ namespace osu.Game.Modes.Objects.Drawables
sample = audio.Sample.Get($@"Gameplay/{sampleSet}-hit{hitType}"); sample = audio.Sample.Get($@"Gameplay/{sampleSet}-hit{hitType}");
} }
protected void PlaySample() protected virtual void PlaySample()
{ {
sample?.Play(); sample?.Play();
} }
@ -85,6 +84,19 @@ namespace osu.Game.Modes.Objects.Drawables
Expire(true); Expire(true);
} }
private List<DrawableHitObject> nestedHitObjects;
protected IEnumerable<DrawableHitObject> NestedHitObjects => nestedHitObjects;
protected void AddNested(DrawableHitObject h)
{
if (nestedHitObjects == null)
nestedHitObjects = new List<DrawableHitObject>();
h.OnJudgement += (d, j) => { OnJudgement?.Invoke(d, j); } ;
nestedHitObjects.Add(h);
}
/// <summary> /// <summary>
/// Process a hit of this hitobject. Carries out judgement. /// Process a hit of this hitobject. Carries out judgement.
/// </summary> /// </summary>
@ -119,7 +131,11 @@ namespace osu.Game.Modes.Objects.Drawables
protected virtual void CheckJudgement(bool userTriggered) protected virtual void CheckJudgement(bool userTriggered)
{ {
//todo: consider making abstract. if (NestedHitObjects != null)
{
foreach (var d in NestedHitObjects)
d.CheckJudgement(userTriggered);
}
} }
protected override void UpdateAfterChildren() protected override void UpdateAfterChildren()

View File

@ -32,6 +32,8 @@ namespace osu.Game.Modes
public abstract HitObjectParser CreateHitObjectParser(); public abstract HitObjectParser CreateHitObjectParser();
public abstract DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap);
public static void Register(Ruleset ruleset) => availableRulesets.TryAdd(ruleset.PlayMode, ruleset.GetType()); public static void Register(Ruleset ruleset) => availableRulesets.TryAdd(ruleset.PlayMode, ruleset.GetType());
protected abstract PlayMode PlayMode { get; } protected abstract PlayMode PlayMode { get; }

View File

@ -1,23 +1,29 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu-framework/master/LICENCE
using OpenTK; using System;
using OpenTK.Graphics; using osu.Framework.Allocation;
using osu.Framework.Configuration; 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.Graphics.Sprites; using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations; using osu.Framework.Graphics.Transformations;
using System; using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Modes.UI namespace osu.Game.Modes.UI
{ {
public class HealthDisplay : Container public class HealthDisplay : Container
{ {
private Box background; private Box background;
private Box fill; private Container fill;
public BindableDouble Current = new BindableDouble() { MinValue = 0, MaxValue = 1 }; public BindableDouble Current = new BindableDouble()
{
MinValue = 0,
MaxValue = 1
};
public HealthDisplay() public HealthDisplay()
{ {
@ -26,19 +32,38 @@ namespace osu.Game.Modes.UI
background = new Box background = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray, Colour = Color4.Black,
}, },
fill = new Box fill = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Scale = new Vector2(0, 1), Scale = new Vector2(0, 1),
}, Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
}
}
},
}; };
Current.ValueChanged += current_ValueChanged; Current.ValueChanged += current_ValueChanged;
} }
[BackgroundDependencyLoader]
private void laod(OsuColour colours)
{
fill.Colour = colours.BlueLighter;
fill.EdgeEffect = new EdgeEffect
{
Colour = colours.BlueDarker.Opacity(0.6f),
Radius = 8,
Type= EdgeEffectType.Glow
};
}
private void current_ValueChanged(object sender, EventArgs e) private void current_ValueChanged(object sender, EventArgs e)
{ {
fill.ScaleTo(new Vector2((float)Current, 1), 200, EasingTypes.OutQuint); fill.ScaleTo(new Vector2((float)Current, 1), 200, EasingTypes.OutQuint);

View File

@ -27,9 +27,9 @@ namespace osu.Game.Modes.UI
protected abstract ScoreCounter CreateScoreCounter(); protected abstract ScoreCounter CreateScoreCounter();
protected virtual HealthDisplay CreateHealthDisplay() => new HealthDisplay protected virtual HealthDisplay CreateHealthDisplay() => new HealthDisplay
{ {
Size = new Vector2(0.5f, 20), Size = new Vector2(1, 5),
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
Padding = new MarginPadding(5) Margin = new MarginPadding { Top = 20 }
}; };
public virtual void OnHit(HitObject h) public virtual void OnHit(HitObject h)

View File

@ -196,7 +196,10 @@ namespace osu.Game.Online.API
Logger.Log($@"Performing request {req}", LoggingTarget.Network); Logger.Log($@"Performing request {req}", LoggingTarget.Network);
req.Perform(this); req.Perform(this);
State = APIState.Online; //we could still be in initialisation, at which point we don't want to say we're Online yet.
if (LocalUser.Value != null)
State = APIState.Online;
failureCount = 0; failureCount = 0;
return true; return true;
} }

View File

@ -3,12 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Newtonsoft.Json; using Newtonsoft.Json;
using osu.Framework.Configuration;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
namespace osu.Game.Online.Chat namespace osu.Game.Online.Chat
{ {
@ -30,16 +25,16 @@ namespace osu.Game.Online.Chat
//internal bool Joined; //internal bool Joined;
public const int MAX_HISTORY = 100; public const int MAX_HISTORY = 300;
[JsonConstructor] [JsonConstructor]
public Channel() public Channel()
{ {
} }
public event Action<Message[]> NewMessagesArrived; public event Action<IEnumerable<Message>> NewMessagesArrived;
public void AddNewMessages(params Message[] messages) public void AddNewMessages(IEnumerable<Message> messages)
{ {
Messages.AddRange(messages); Messages.AddRange(messages);
purgeOldMessages(); purgeOldMessages();

View File

@ -1,10 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives; using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using OpenTK; using OpenTK;
using OpenTK.Graphics; using OpenTK.Graphics;
@ -15,6 +17,50 @@ namespace osu.Game.Online.Chat.Drawables
{ {
public readonly Message Message; public readonly Message Message;
private static readonly Color4[] username_colours = {
OsuColour.FromHex("588c7e"),
OsuColour.FromHex("b2a367"),
OsuColour.FromHex("c98f65"),
OsuColour.FromHex("bc5151"),
OsuColour.FromHex("5c8bd6"),
OsuColour.FromHex("7f6ab7"),
OsuColour.FromHex("a368ad"),
OsuColour.FromHex("aa6880"),
OsuColour.FromHex("6fad9b"),
OsuColour.FromHex("f2e394"),
OsuColour.FromHex("f2ae72"),
OsuColour.FromHex("f98f8a"),
OsuColour.FromHex("7daef4"),
OsuColour.FromHex("a691f2"),
OsuColour.FromHex("c894d3"),
OsuColour.FromHex("d895b0"),
OsuColour.FromHex("53c4a1"),
OsuColour.FromHex("eace5c"),
OsuColour.FromHex("ea8c47"),
OsuColour.FromHex("fc4f4f"),
OsuColour.FromHex("3d94ea"),
OsuColour.FromHex("7760ea"),
OsuColour.FromHex("af52c6"),
OsuColour.FromHex("e25696"),
OsuColour.FromHex("677c66"),
OsuColour.FromHex("9b8732"),
OsuColour.FromHex("8c5129"),
OsuColour.FromHex("8c3030"),
OsuColour.FromHex("1f5d91"),
OsuColour.FromHex("4335a5"),
OsuColour.FromHex("812a96"),
OsuColour.FromHex("992861"),
};
private Color4 getUsernameColour(Message message)
{
//todo: use User instead of Message when user_id is correctly populated.
return username_colours[message.UserId % username_colours.Length];
}
const float padding = 200; const float padding = 200;
const float text_size = 20; const float text_size = 20;
@ -25,6 +71,8 @@ namespace osu.Game.Online.Chat.Drawables
RelativeSizeAxes = Axes.X; RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;
Padding = new MarginPadding { Left = 15, Right = 15 };
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container
@ -34,13 +82,19 @@ namespace osu.Game.Online.Chat.Drawables
{ {
new OsuSpriteText new OsuSpriteText
{ {
Text = Message.Timestamp.LocalDateTime.ToLongTimeString(), Anchor = Anchor.CentreLeft,
TextSize = text_size, Origin = Anchor.CentreLeft,
Colour = Color4.Gray Font = @"Exo2.0-SemiBold",
Text = $@"{Message.Timestamp.LocalDateTime:hh:mm:ss}",
FixedWidth = true,
TextSize = text_size * 0.75f,
Alpha = 0.4f,
}, },
new OsuSpriteText new OsuSpriteText
{ {
Text = Message.User.Name, Font = @"Exo2.0-BoldItalic",
Text = $@"{Message.User.Name}:",
Colour = getUsernameColour(Message),
TextSize = text_size, TextSize = text_size,
Origin = Anchor.TopRight, Origin = Anchor.TopRight,
Anchor = Anchor.TopRight, Anchor = Anchor.TopRight,
@ -51,7 +105,7 @@ namespace osu.Game.Online.Chat.Drawables
{ {
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Left = padding + 10 }, Padding = new MarginPadding { Left = padding + 15 },
Children = new Drawable[] Children = new Drawable[]
{ {
new OsuSpriteText new OsuSpriteText

View File

@ -4,25 +4,25 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
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.Graphics.Primitives;
using osu.Framework.MathUtils;
using osu.Framework.Threading;
using osu.Game.Graphics.Sprites; using osu.Game.Graphics.Sprites;
using OpenTK; using OpenTK;
namespace osu.Game.Online.Chat.Drawables namespace osu.Game.Online.Chat.Drawables
{ {
public class ChannelDisplay : Container public class DrawableChannel : Container
{ {
private readonly Channel channel; private readonly Channel channel;
private FlowContainer flow; private FlowContainer flow;
private ScrollContainer scroll;
public ChannelDisplay(Channel channel) public DrawableChannel(Channel channel)
{ {
this.channel = channel; this.channel = channel;
newMessages(channel.Messages);
channel.NewMessagesArrived += newMessages;
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -36,8 +36,9 @@ namespace osu.Game.Online.Chat.Drawables
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre Origin = Anchor.Centre
}, },
new ScrollContainer scroll = new ScrollContainer
{ {
RelativeSizeAxes = Axes.Both,
Children = new Drawable[] Children = new Drawable[]
{ {
flow = new FlowContainer flow = new FlowContainer
@ -45,37 +46,54 @@ namespace osu.Game.Online.Chat.Drawables
Direction = FlowDirections.Vertical, Direction = FlowDirections.Vertical,
RelativeSizeAxes = Axes.X, RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y, AutoSizeAxes = Axes.Y,
Spacing = new Vector2(1, 1) Padding = new MarginPadding { Left = 20, Right = 20 }
} }
} }
} }
}; };
channel.NewMessagesArrived += newMessagesArrived;
}
protected override void LoadComplete()
{
base.LoadComplete();
newMessagesArrived(channel.Messages);
scrollToEnd();
} }
protected override void Dispose(bool isDisposing) protected override void Dispose(bool isDisposing)
{ {
base.Dispose(isDisposing); base.Dispose(isDisposing);
channel.NewMessagesArrived -= newMessages; channel.NewMessagesArrived -= newMessagesArrived;
} }
[BackgroundDependencyLoader] private void newMessagesArrived(IEnumerable<Message> newMessages)
private void load()
{
newMessages(channel.Messages);
}
private void newMessages(IEnumerable<Message> newMessages)
{ {
if (!IsLoaded) return; if (!IsLoaded) return;
var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY)); var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY));
if (scroll.IsScrolledToEnd(10) || !flow.Children.Any())
scrollToEnd();
//up to last Channel.MAX_HISTORY messages //up to last Channel.MAX_HISTORY messages
foreach (Message m in displayMessages) foreach (Message m in displayMessages)
flow.Add(new ChatLine(m)); {
var d = new ChatLine(m);
flow.Add(d);
}
while (flow.Children.Count() > Channel.MAX_HISTORY) while (flow.Children.Count(c => c.LifetimeEnd == double.MaxValue) > Channel.MAX_HISTORY)
flow.Remove(flow.Children.First()); {
var d = flow.Children.First(c => c.LifetimeEnd == double.MaxValue);
if (!scroll.IsScrolledToEnd(10))
scroll.OffsetScrollPosition(-d.DrawHeight);
d.Expire();
}
} }
private void scrollToEnd() => Scheduler.AddDelayed(() => scroll.ScrollToEnd(), 50);
} }
} }

View File

@ -11,6 +11,7 @@ namespace osu.Game.Online.Chat
[JsonProperty(@"message_id")] [JsonProperty(@"message_id")]
public long Id; public long Id;
//todo: this should be inside sender.
[JsonProperty(@"user_id")] [JsonProperty(@"user_id")]
public int UserId; public int UserId;

View File

@ -3,7 +3,7 @@
using System; using System;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.GameModes; using osu.Framework.Screens;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
@ -31,6 +31,8 @@ namespace osu.Game
{ {
public class OsuGame : OsuGameBase public class OsuGame : OsuGameBase
{ {
public virtual bool IsDeployedBuild => false;
public Toolbar Toolbar; public Toolbar Toolbar;
private ChatOverlay chat; private ChatOverlay chat;
@ -39,10 +41,18 @@ namespace osu.Game
private NotificationManager notificationManager; private NotificationManager notificationManager;
private MainMenu mainMenu => modeStack?.ChildGameMode as MainMenu; private Intro intro
private Intro intro => modeStack as Intro; {
get
{
Screen s = screenStack;
while (s != null && !(s is Intro))
s = s.ChildScreen;
return s as Intro;
}
}
private OsuGameMode modeStack; private OsuScreen screenStack;
private VolumeControl volume; private VolumeControl volume;
@ -106,11 +116,11 @@ namespace osu.Game
} }
}); });
(modeStack = new Intro()).Preload(this, d => (screenStack = new Loader()).Preload(this, d =>
{ {
modeStack.ModePushed += modeAdded; screenStack.ModePushed += screenAdded;
modeStack.Exited += modeRemoved; screenStack.Exited += screenRemoved;
mainContent.Add(modeStack); mainContent.Add(screenStack);
}); });
//overlay elements //overlay elements
@ -148,7 +158,7 @@ namespace osu.Game
(Toolbar = new Toolbar (Toolbar = new Toolbar
{ {
Depth = -3, Depth = -3,
OnHome = delegate { mainMenu?.MakeCurrent(); }, OnHome = delegate { intro?.ChildScreen?.MakeCurrent(); },
OnPlayModeChange = delegate (PlayMode m) { PlayMode.Value = m; }, OnPlayModeChange = delegate (PlayMode m) { PlayMode.Value = m; },
}).Preload(this, t => }).Preload(this, t =>
{ {
@ -206,20 +216,16 @@ namespace osu.Game
return base.OnKeyDown(state, args); return base.OnKeyDown(state, args);
} }
public Action<GameMode> ModeChanged; public event Action<Screen> ModeChanged;
private Container mainContent; private Container mainContent;
private Container overlayContent; private Container overlayContent;
private void modeChanged(GameMode newMode) private void modeChanged(Screen newScreen)
{ {
// - Ability to change window size
// - Ability to adjust music playback
// - Frame limiter changes
//central game mode change logic. //central game mode change logic.
if ((newMode as OsuGameMode)?.ShowOverlays != true) if ((newScreen as OsuScreen)?.ShowOverlays != true)
{ {
Toolbar.State = Visibility.Hidden; Toolbar.State = Visibility.Hidden;
musicController.State = Visibility.Hidden; musicController.State = Visibility.Hidden;
@ -230,22 +236,24 @@ namespace osu.Game
Toolbar.State = Visibility.Visible; Toolbar.State = Visibility.Visible;
} }
Cursor.FadeIn(100); if (newScreen is MainMenu)
Cursor.FadeIn(100);
ModeChanged?.Invoke(newMode); ModeChanged?.Invoke(newScreen);
if (newMode == null) if (newScreen == null)
Host.Exit(); Exit();
} }
protected override bool OnExiting() protected override bool OnExiting()
{ {
if (!intro.DidLoadMenu || intro.ChildGameMode != null) if (screenStack.ChildScreen == null) return false;
if (intro == null) return true;
if (!intro.DidLoadMenu || intro.ChildScreen != null)
{ {
Scheduler.Add(delegate Scheduler.Add(intro.MakeCurrent);
{
intro.MakeCurrent();
});
return true; return true;
} }
@ -256,21 +264,21 @@ namespace osu.Game
{ {
base.UpdateAfterChildren(); base.UpdateAfterChildren();
if (modeStack.ChildGameMode != null) if (intro?.ChildScreen != null)
modeStack.ChildGameMode.Padding = new MarginPadding { Top = Toolbar.Position.Y + Toolbar.DrawHeight }; intro.ChildScreen.Padding = new MarginPadding { Top = Toolbar.Position.Y + Toolbar.DrawHeight };
} }
private void modeAdded(GameMode newMode) private void screenAdded(Screen newScreen)
{ {
newMode.ModePushed += modeAdded; newScreen.ModePushed += screenAdded;
newMode.Exited += modeRemoved; newScreen.Exited += screenRemoved;
modeChanged(newMode); modeChanged(newScreen);
} }
private void modeRemoved(GameMode newMode) private void screenRemoved(Screen newScreen)
{ {
modeChanged(newMode); modeChanged(newScreen);
} }
} }
} }

View File

@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using OpenTK; using OpenTK;
using OpenTK.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;
@ -19,12 +18,19 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests; using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat; using osu.Game.Online.Chat;
using osu.Game.Online.Chat.Drawables; using osu.Game.Online.Chat.Drawables;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.UserInterface;
using OpenTK.Graphics;
using osu.Framework.Input;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
public class ChatOverlay : OverlayContainer, IOnlineComponent public class ChatOverlay : FocusedOverlayContainer, IOnlineComponent
{ {
private ChannelDisplay channelDisplay; const float textbox_height = 40;
private DrawableChannel channelDisplay;
private ScheduledDelegate messageRequest; private ScheduledDelegate messageRequest;
@ -32,6 +38,8 @@ namespace osu.Game.Overlays
protected override Container<Drawable> Content => content; protected override Container<Drawable> Content => content;
private FocusedTextBox inputTextBox;
private APIAccess api; private APIAccess api;
public ChatOverlay() public ChatOverlay()
@ -47,15 +55,65 @@ namespace osu.Game.Overlays
{ {
Depth = float.MaxValue, Depth = float.MaxValue,
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.1f).Opacity(0.4f), Colour = Color4.Black,
Alpha = 0.9f,
}, },
content = new Container content = new Container
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Top = 5, Bottom = textbox_height + 5 },
},
new Container
{
Anchor = Anchor.BottomLeft,
Origin = Anchor.BottomLeft,
RelativeSizeAxes = Axes.X,
Height = textbox_height,
Padding = new MarginPadding(5),
Children = new Drawable[]
{
inputTextBox = new FocusedTextBox
{
RelativeSizeAxes = Axes.Both,
Height = 1,
PlaceholderText = "type your message",
Exit = () => State = Visibility.Hidden,
OnCommit = postMessage,
HoldFocus = true,
}
}
} }
}); });
} }
protected override bool OnFocus(InputState state)
{
//this is necessary as inputTextBox is masked away and therefore can't get focus :(
inputTextBox.TriggerFocus();
return false;
}
private void postMessage(TextBox sender, bool newText)
{
var postText = sender.Text;
if (!string.IsNullOrEmpty(postText))
{
//todo: actually send to server
careChannels.FirstOrDefault()?.AddNewMessages(new[]
{
new Message
{
User = api.LocalUser.Value,
Timestamp = DateTimeOffset.Now,
Content = postText
}
});
}
sender.Text = string.Empty;
}
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(APIAccess api) private void load(APIAccess api)
{ {
@ -69,7 +127,7 @@ namespace osu.Game.Overlays
private void addChannel(Channel channel) private void addChannel(Channel channel)
{ {
Add(channelDisplay = new ChannelDisplay(channel)); Add(channelDisplay = new DrawableChannel(channel));
careChannels.Add(channel); careChannels.Add(channel);
} }
@ -82,10 +140,11 @@ namespace osu.Game.Overlays
fetchReq = new GetMessagesRequest(careChannels, lastMessageId); fetchReq = new GetMessagesRequest(careChannels, lastMessageId);
fetchReq.Success += delegate (List<Message> messages) fetchReq.Success += delegate (List<Message> messages)
{ {
foreach (Message m in messages) var ids = messages.Select(m => m.ChannelId).Distinct();
{
careChannels.Find(c => c.Id == m.ChannelId).AddNewMessages(m); //batch messages per channel.
} foreach (var id in ids)
careChannels.Find(c => c.Id == id)?.AddNewMessages(messages.Where(m => m.ChannelId == id));
lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId; lastMessageId = messages.LastOrDefault()?.Id ?? lastMessageId;
@ -151,6 +210,8 @@ namespace osu.Game.Overlays
ListChannelsRequest req = new ListChannelsRequest(); ListChannelsRequest req = new ListChannelsRequest();
req.Success += delegate (List<Channel> channels) req.Success += delegate (List<Channel> channels)
{ {
Debug.Assert(careChannels.Count == 0);
Scheduler.Add(delegate Scheduler.Add(delegate
{ {
loading.FadeOut(100); loading.FadeOut(100);

View File

@ -38,8 +38,9 @@ namespace osu.Game.Overlays
Colour = Color4.Black, Colour = Color4.Black,
Alpha = 0.6f, Alpha = 0.6f,
}, },
scrollContainer = new ScrollContainer() scrollContainer = new ScrollContainer
{ {
RelativeSizeAxes = Axes.Both,
Margin = new MarginPadding { Top = Toolbar.Toolbar.HEIGHT }, Margin = new MarginPadding { Top = Toolbar.Toolbar.HEIGHT },
Children = new[] Children = new[]
{ {

View File

@ -89,7 +89,6 @@ namespace osu.Game.Overlays.Notifications
IconContent = new Container IconContent = new Container
{ {
Size = new Vector2(40), Size = new Vector2(40),
Colour = Color4.DarkGray,
Masking = true, Masking = true,
CornerRadius = 5, CornerRadius = 5,
}, },

View File

@ -1,7 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Framework.Graphics.Colour;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Notifications namespace osu.Game.Overlays.Notifications
{ {
@ -14,5 +18,11 @@ namespace osu.Game.Overlays.Notifications
this.progressNotification = progressNotification; this.progressNotification = progressNotification;
Icon = FontAwesome.fa_check; Icon = FontAwesome.fa_check;
} }
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
IconBackgound.ColourInfo = ColourInfo.GradientVertical(colours.GreenDark, colours.GreenLight);
}
} }
} }

View File

@ -37,19 +37,21 @@ namespace osu.Game.Overlays.Notifications
private SpriteText textDrawable; private SpriteText textDrawable;
private TextAwesome iconDrawable; private TextAwesome iconDrawable;
protected Box IconBackgound;
public SimpleNotification() public SimpleNotification()
{ {
IconContent.Add(new Drawable[] IconContent.Add(new Drawable[]
{ {
new Box IconBackgound = new Box
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
ColourInfo = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.5f)) ColourInfo = ColourInfo.GradientVertical(OsuColour.Gray(0.2f), OsuColour.Gray(0.6f))
}, },
iconDrawable = new TextAwesome iconDrawable = new TextAwesome
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Icon = icon , Icon = icon,
} }
}); });

View File

@ -74,6 +74,7 @@ namespace osu.Game.Overlays.Options
{ {
Content.Anchor = Anchor.CentreLeft; Content.Anchor = Anchor.CentreLeft;
Content.Origin = Anchor.CentreLeft; Content.Origin = Anchor.CentreLeft;
RelativeSizeAxes = Axes.Both;
} }
} }
} }

View File

@ -55,7 +55,7 @@ namespace osu.Game.Overlays.Pause
} }
} }
public AudioSample SampleClick, SampleHover; public SampleChannel SampleClick, SampleHover;
private Container backgroundContainer, colourContainer, glowContainer; private Container backgroundContainer, colourContainer, glowContainer;
private Box leftGlow, centerGlow, rightGlow; private Box leftGlow, centerGlow, rightGlow;

View File

@ -66,7 +66,7 @@ namespace osu.Game.Overlays.Toolbar
private SpriteText tooltip1; private SpriteText tooltip1;
private SpriteText tooltip2; private SpriteText tooltip2;
protected FlowContainer Flow; protected FlowContainer Flow;
private AudioSample sampleClick; private SampleChannel sampleClick;
public ToolbarButton() public ToolbarButton()
{ {

View File

@ -5,7 +5,7 @@ using System;
using System.Threading; using System.Threading;
using osu.Framework; using osu.Framework;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.GameModes; using osu.Framework.Screens;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Transformations; using osu.Framework.Graphics.Transformations;
using osu.Framework.Input; using osu.Framework.Input;
@ -13,9 +13,9 @@ using OpenTK;
namespace osu.Game.Screens namespace osu.Game.Screens
{ {
public abstract class BackgroundMode : GameMode, IEquatable<BackgroundMode> public abstract class BackgroundScreen : Screen, IEquatable<BackgroundScreen>
{ {
public virtual bool Equals(BackgroundMode other) public virtual bool Equals(BackgroundScreen other)
{ {
return other?.GetType() == GetType(); return other?.GetType() == GetType();
} }
@ -37,21 +37,21 @@ namespace osu.Game.Screens
this.game = game; this.game = game;
} }
public override bool Push(GameMode mode) public override bool Push(Screen screen)
{ {
// When trying to push a non-loaded GameMode, load it asynchronously and re-invoke Push // When trying to push a non-loaded GameMode, load it asynchronously and re-invoke Push
// once it's done. // once it's done.
if (mode.LoadState == LoadState.NotLoaded) if (screen.LoadState == LoadState.NotLoaded)
{ {
mode.Preload(game, d => Push((BackgroundMode)d)); screen.Preload(game, d => Push((BackgroundScreen)d));
return true; return true;
} }
// Make sure the in-progress loading is complete before pushing the GameMode. // Make sure the in-progress loading is complete before pushing the GameMode.
while (mode.LoadState < LoadState.Loaded) while (screen.LoadState < LoadState.Loaded)
Thread.Sleep(1); Thread.Sleep(1);
base.Push(mode); base.Push(screen);
return true; return true;
} }
@ -62,7 +62,7 @@ namespace osu.Game.Screens
Content.Scale = new Vector2(1 + (x_movement_amount / DrawSize.X) * 2); Content.Scale = new Vector2(1 + (x_movement_amount / DrawSize.X) * 2);
} }
protected override void OnEntering(GameMode last) protected override void OnEntering(Screen last)
{ {
Content.FadeOut(); Content.FadeOut();
Content.MoveToX(x_movement_amount); Content.MoveToX(x_movement_amount);
@ -73,13 +73,13 @@ namespace osu.Game.Screens
base.OnEntering(last); base.OnEntering(last);
} }
protected override void OnSuspending(GameMode next) protected override void OnSuspending(Screen next)
{ {
Content.MoveToX(-x_movement_amount, transition_length, EasingTypes.InOutQuart); Content.MoveToX(-x_movement_amount, transition_length, EasingTypes.InOutQuart);
base.OnSuspending(next); base.OnSuspending(next);
} }
protected override bool OnExiting(GameMode next) protected override bool OnExiting(Screen next)
{ {
Content.FadeOut(transition_length, EasingTypes.OutExpo); Content.FadeOut(transition_length, EasingTypes.OutExpo);
Content.MoveToX(x_movement_amount, transition_length, EasingTypes.OutExpo); Content.MoveToX(x_movement_amount, transition_length, EasingTypes.OutExpo);
@ -87,7 +87,7 @@ namespace osu.Game.Screens
return base.OnExiting(next); return base.OnExiting(next);
} }
protected override void OnResuming(GameMode last) protected override void OnResuming(Screen last)
{ {
Content.MoveToX(0, transition_length, EasingTypes.OutExpo); Content.MoveToX(0, transition_length, EasingTypes.OutExpo);
base.OnResuming(last); base.OnResuming(last);

View File

@ -9,7 +9,7 @@ using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Screens.Backgrounds namespace osu.Game.Screens.Backgrounds
{ {
public class BackgroundModeBeatmap : BackgroundMode public class BackgroundScreenBeatmap : BackgroundScreen
{ {
private Background background; private Background background;
@ -55,7 +55,7 @@ namespace osu.Game.Screens.Backgrounds
} }
} }
public BackgroundModeBeatmap(WorkingBeatmap beatmap) public BackgroundScreenBeatmap(WorkingBeatmap beatmap)
{ {
Beatmap = beatmap; Beatmap = beatmap;
} }
@ -66,9 +66,9 @@ namespace osu.Game.Screens.Backgrounds
blurTarget = sigma; blurTarget = sigma;
} }
public override bool Equals(BackgroundMode other) public override bool Equals(BackgroundScreen other)
{ {
return base.Equals(other) && beatmap == ((BackgroundModeBeatmap)other).Beatmap; return base.Equals(other) && beatmap == ((BackgroundScreenBeatmap)other).Beatmap;
} }
class BeatmapBackground : Background class BeatmapBackground : Background

View File

@ -5,19 +5,19 @@ using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Screens.Backgrounds namespace osu.Game.Screens.Backgrounds
{ {
public class BackgroundModeCustom : BackgroundMode public class BackgroundScreenCustom : BackgroundScreen
{ {
private readonly string textureName; private readonly string textureName;
public BackgroundModeCustom(string textureName) public BackgroundScreenCustom(string textureName)
{ {
this.textureName = textureName; this.textureName = textureName;
Add(new Background(textureName)); Add(new Background(textureName));
} }
public override bool Equals(BackgroundMode other) public override bool Equals(BackgroundScreen other)
{ {
return base.Equals(other) && textureName == ((BackgroundModeCustom)other).textureName; return base.Equals(other) && textureName == ((BackgroundScreenCustom)other).textureName;
} }
} }
} }

View File

@ -7,7 +7,7 @@ using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Screens.Backgrounds namespace osu.Game.Screens.Backgrounds
{ {
public class BackgroundModeDefault : BackgroundMode public class BackgroundScreenDefault : BackgroundScreen
{ {
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(BaseGame game) private void load(BaseGame game)

View File

@ -3,7 +3,7 @@
namespace osu.Game.Screens.Backgrounds namespace osu.Game.Screens.Backgrounds
{ {
public class BackgroundModeEmpty : BackgroundMode public class BackgroundScreenEmpty : BackgroundScreen
{ {
} }

View File

@ -3,7 +3,7 @@
namespace osu.Game.Screens.Charts namespace osu.Game.Screens.Charts
{ {
class ChartInfo : GameModeWhiteBox class ChartInfo : ScreenWhiteBox
{ {
} }
} }

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
namespace osu.Game.Screens.Charts namespace osu.Game.Screens.Charts
{ {
class ChartListing : GameModeWhiteBox class ChartListing : ScreenWhiteBox
{ {
protected override IEnumerable<Type> PossibleChildren => new[] { protected override IEnumerable<Type> PossibleChildren => new[] {
typeof(ChartInfo) typeof(ChartInfo)

View File

@ -3,7 +3,7 @@
namespace osu.Game.Screens.Direct namespace osu.Game.Screens.Direct
{ {
class OnlineListing : GameModeWhiteBox class OnlineListing : ScreenWhiteBox
{ {
} }
} }

View File

@ -1,23 +1,23 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.GameModes; using osu.Framework.Screens;
using osu.Game.Screens.Backgrounds; using osu.Game.Screens.Backgrounds;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game.Screens.Edit namespace osu.Game.Screens.Edit
{ {
class Editor : GameModeWhiteBox class Editor : ScreenWhiteBox
{ {
protected override BackgroundMode CreateBackground() => new BackgroundModeCustom(@"Backgrounds/bg4"); protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg4");
protected override void OnEntering(GameMode last) protected override void OnEntering(Screen last)
{ {
base.OnEntering(last); base.OnEntering(last);
Background.Schedule(() => Background.FadeColour(Color4.DarkGray, 500)); Background.Schedule(() => Background.FadeColour(Color4.DarkGray, 500));
} }
protected override bool OnExiting(GameMode next) protected override bool OnExiting(Screen next)
{ {
Background.Schedule(() => Background.FadeColour(Color4.White, 500)); Background.Schedule(() => Background.FadeColour(Color4.White, 500));
return base.OnExiting(next); return base.OnExiting(next);

View File

@ -3,7 +3,7 @@
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.GameModes; using osu.Framework.Screens;
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.Graphics.Sprites;
@ -17,7 +17,7 @@ using OpenTK.Graphics;
namespace osu.Game.Screens namespace osu.Game.Screens
{ {
public class GameModeWhiteBox : OsuGameMode public class ScreenWhiteBox : OsuScreen
{ {
private BackButton popButton; private BackButton popButton;
@ -29,9 +29,9 @@ namespace osu.Game.Screens
private Container textContainer; private Container textContainer;
private Box box; private Box box;
protected override BackgroundMode CreateBackground() => new BackgroundModeCustom(@"Backgrounds/bg2"); protected override BackgroundScreen CreateBackground() => new BackgroundScreenCustom(@"Backgrounds/bg2");
protected override void OnEntering(GameMode last) protected override void OnEntering(Screen last)
{ {
base.OnEntering(last); base.OnEntering(last);
@ -54,7 +54,7 @@ namespace osu.Game.Screens
Content.FadeIn(transition_time, EasingTypes.OutExpo); Content.FadeIn(transition_time, EasingTypes.OutExpo);
} }
protected override bool OnExiting(GameMode next) protected override bool OnExiting(Screen next)
{ {
textContainer.MoveTo(new Vector2((DrawSize.X / 16), 0), transition_time, EasingTypes.OutExpo); textContainer.MoveTo(new Vector2((DrawSize.X / 16), 0), transition_time, EasingTypes.OutExpo);
Content.FadeOut(transition_time, EasingTypes.OutExpo); Content.FadeOut(transition_time, EasingTypes.OutExpo);
@ -62,7 +62,7 @@ namespace osu.Game.Screens
return base.OnExiting(next); return base.OnExiting(next);
} }
protected override void OnSuspending(GameMode next) protected override void OnSuspending(Screen next)
{ {
base.OnSuspending(next); base.OnSuspending(next);
@ -70,7 +70,7 @@ namespace osu.Game.Screens
Content.FadeOut(transition_time, EasingTypes.OutExpo); Content.FadeOut(transition_time, EasingTypes.OutExpo);
} }
protected override void OnResuming(GameMode last) protected override void OnResuming(Screen last)
{ {
base.OnResuming(last); base.OnResuming(last);
@ -78,7 +78,7 @@ namespace osu.Game.Screens
Content.FadeIn(transition_time, EasingTypes.OutExpo); Content.FadeIn(transition_time, EasingTypes.OutExpo);
} }
public GameModeWhiteBox() public ScreenWhiteBox()
{ {
Children = new Drawable[] Children = new Drawable[]
{ {
@ -148,7 +148,7 @@ namespace osu.Game.Screens
BackgroundColour = getColourFor(t), BackgroundColour = getColourFor(t),
Action = delegate Action = delegate
{ {
Push(Activator.CreateInstance(t) as GameMode); Push(Activator.CreateInstance(t) as Screen);
} }
}); });
} }

View File

@ -0,0 +1,28 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Screens;
using osu.Game.Screens.Menu;
namespace osu.Game.Screens
{
class Loader : OsuScreen
{
internal override bool ShowOverlays => false;
public Loader()
{
ValidForResume = false;
}
[BackgroundDependencyLoader]
private void load(OsuGame game)
{
if (game.IsDeployedBuild)
new Disclaimer().Preload(game, d => Push((Screen)d));
else
new Intro().Preload(game, d => Push((Screen)d));
}
}
}

View File

@ -37,7 +37,7 @@ namespace osu.Game.Screens.Menu
private readonly float extraWidth; private readonly float extraWidth;
private Key triggerKey; private Key triggerKey;
private string text; private string text;
private AudioSample sampleClick; private SampleChannel sampleClick;
public override bool Contains(Vector2 screenSpacePos) public override bool Contains(Vector2 screenSpacePos)
{ {

View File

@ -32,8 +32,6 @@ namespace osu.Game.Screens.Menu
public Action OnChart; public Action OnChart;
public Action OnTest; public Action OnTest;
private AudioSample sampleOsuClick;
private Toolbar toolbar; private Toolbar toolbar;
private FlowContainerWithOrigin buttonFlow; private FlowContainerWithOrigin buttonFlow;
@ -122,7 +120,6 @@ namespace osu.Game.Screens.Menu
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(AudioManager audio, OsuGame game = null) private void load(AudioManager audio, OsuGame game = null)
{ {
sampleOsuClick = audio.Sample.Get(@"Menu/menuhit");
toolbar = game?.Toolbar; toolbar = game?.Toolbar;
} }
@ -181,7 +178,6 @@ namespace osu.Game.Screens.Menu
switch (state) switch (state)
{ {
case MenuState.Initial: case MenuState.Initial:
sampleOsuClick.Play();
State = MenuState.TopLevel; State = MenuState.TopLevel;
return; return;
case MenuState.TopLevel: case MenuState.TopLevel:
@ -218,6 +214,7 @@ namespace osu.Game.Screens.Menu
switch (state) switch (state)
{ {
case MenuState.Exit:
case MenuState.Initial: case MenuState.Initial:
toolbar?.Hide(); toolbar?.Hide();
@ -233,6 +230,12 @@ namespace osu.Game.Screens.Menu
foreach (Button b in buttonsPlay) foreach (Button b in buttonsPlay)
b.State = ButtonState.Contracted; b.State = ButtonState.Contracted;
if (state == MenuState.Exit)
{
osuLogo.RotateTo(20, EXIT_DELAY * 1.5f);
osuLogo.FadeOut(EXIT_DELAY);
}
break; break;
case MenuState.TopLevel: case MenuState.TopLevel:
buttonArea.Flush(true); buttonArea.Flush(true);
@ -276,21 +279,6 @@ namespace osu.Game.Screens.Menu
foreach (Button b in buttonsPlay) foreach (Button b in buttonsPlay)
b.State = ButtonState.Contracted; b.State = ButtonState.Contracted;
break; break;
case MenuState.Exit:
buttonArea.FadeOut(200);
foreach (Button b in buttonsTopLevel)
b.State = ButtonState.Contracted;
foreach (Button b in buttonsPlay)
b.State = ButtonState.Contracted;
osuLogo.Delay(150);
osuLogo.ScaleTo(1f, EXIT_DELAY * 1.5f);
osuLogo.RotateTo(20, EXIT_DELAY * 1.5f);
osuLogo.FadeOut(EXIT_DELAY);
break;
} }
backButton.State = state == MenuState.Play ? ButtonState.Expanded : ButtonState.Contracted; backButton.State = state == MenuState.Play ? ButtonState.Expanded : ButtonState.Contracted;

View File

@ -0,0 +1,119 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Screens;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Screens.Menu
{
class Disclaimer : OsuScreen
{
private Intro intro;
private TextAwesome icon;
private Color4 iconColour;
internal override bool ShowOverlays => false;
public Disclaimer()
{
ValidForResume = false;
Children = new Drawable[]
{
new FlowContainer
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FlowDirections.Vertical,
Spacing = new Vector2(0, 2),
Children = new Drawable[]
{
icon = new TextAwesome
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Icon = FontAwesome.fa_warning,
TextSize = 30,
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
TextSize = 30,
Text = "This is a development build",
Margin = new MarginPadding
{
Bottom = 20
},
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Don't expect shit to work perfectly as this is very much a work in progress."
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Don't report bugs because we are aware. Don't complain about missing features because we are adding them."
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
Text = "Sit back and enjoy. Visit discord.gg/ppy if you want to help out!",
Margin = new MarginPadding { Bottom = 20 },
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre,
TextSize = 12,
Text = "oh and yes, you will get seizures.",
},
}
}
};
}
[BackgroundDependencyLoader]
private void load(OsuGame game, OsuColour colours)
{
(intro = new Intro()).Preload(game);
iconColour = colours.Yellow;
}
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
Content.FadeInFromZero(500);
icon.Delay(1500);
icon.FadeColour(iconColour, 200);
Delay(6000, true);
Content.FadeOut(250);
Delay(250);
Schedule(() => Push(intro));
}
}
}

View File

@ -5,7 +5,7 @@ using osu.Framework.Allocation;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Audio.Track; using osu.Framework.Audio.Track;
using osu.Framework.GameModes; using osu.Framework.Screens;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Transformations; using osu.Framework.Graphics.Transformations;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -14,7 +14,7 @@ using OpenTK.Graphics;
namespace osu.Game.Screens.Menu namespace osu.Game.Screens.Menu
{ {
class Intro : OsuGameMode public class Intro : OsuScreen
{ {
private OsuLogo logo; private OsuLogo logo;
@ -24,13 +24,13 @@ namespace osu.Game.Screens.Menu
internal bool DidLoadMenu; internal bool DidLoadMenu;
MainMenu mainMenu; MainMenu mainMenu;
private AudioSample welcome; private SampleChannel welcome;
private AudioSample seeya; private SampleChannel seeya;
private AudioTrack bgm; private Track bgm;
internal override bool ShowOverlays => (ParentGameMode as OsuGameMode)?.ShowOverlays ?? false; internal override bool ShowOverlays => false;
protected override BackgroundMode CreateBackground() => new BackgroundModeEmpty(); protected override BackgroundScreen CreateBackground() => new BackgroundScreenEmpty();
public Intro() public Intro()
{ {
@ -65,7 +65,7 @@ namespace osu.Game.Screens.Menu
bgm.Looping = true; bgm.Looping = true;
} }
protected override void OnEntering(GameMode last) protected override void OnEntering(Screen last)
{ {
base.OnEntering(last); base.OnEntering(last);
@ -77,8 +77,7 @@ namespace osu.Game.Screens.Menu
{ {
bgm.Start(); bgm.Start();
mainMenu = new MainMenu(); (mainMenu = new MainMenu()).Preload(Game);
mainMenu.Preload(Game);
Scheduler.AddDelayed(delegate Scheduler.AddDelayed(delegate
{ {
@ -95,24 +94,27 @@ namespace osu.Game.Screens.Menu
logo.FadeIn(20000, EasingTypes.OutQuint); logo.FadeIn(20000, EasingTypes.OutQuint);
} }
protected override void OnSuspending(GameMode next) protected override void OnSuspending(Screen next)
{ {
Content.FadeOut(300); Content.FadeOut(300);
base.OnSuspending(next); base.OnSuspending(next);
} }
protected override bool OnExiting(GameMode next) protected override bool OnExiting(Screen next)
{ {
//cancel exiting if we haven't loaded the menu yet. //cancel exiting if we haven't loaded the menu yet.
return !DidLoadMenu; return !DidLoadMenu;
} }
protected override void OnResuming(GameMode last) protected override void OnResuming(Screen last)
{ {
if (!(last is MainMenu))
Content.FadeIn(300);
//we also handle the exit transition. //we also handle the exit transition.
seeya.Play(); seeya.Play();
double fadeOutTime = (last.LifetimeEnd - Time.Current) + 100; double fadeOutTime = 2000;
Scheduler.AddDelayed(Exit, fadeOutTime); Scheduler.AddDelayed(Exit, fadeOutTime);

View File

@ -2,8 +2,8 @@
// 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.Allocation;
using osu.Framework.GameModes; using osu.Framework.Screens;
using osu.Framework.GameModes.Testing; using osu.Framework.Screens.Testing;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Transformations; using osu.Framework.Graphics.Transformations;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
@ -16,20 +16,20 @@ using osu.Game.Screens.Select;
namespace osu.Game.Screens.Menu namespace osu.Game.Screens.Menu
{ {
public class MainMenu : OsuGameMode public class MainMenu : OsuScreen
{ {
private ButtonSystem buttons; private ButtonSystem buttons;
public override string Name => @"Main Menu"; public override string Name => @"Main Menu";
internal override bool ShowOverlays => buttons.State != MenuState.Initial; internal override bool ShowOverlays => buttons.State != MenuState.Initial;
private BackgroundMode background; private BackgroundScreen background;
protected override BackgroundMode CreateBackground() => background; protected override BackgroundScreen CreateBackground() => background;
public MainMenu() public MainMenu()
{ {
background = new BackgroundModeDefault(); background = new BackgroundScreenDefault();
Children = new Drawable[] Children = new Drawable[]
{ {
@ -59,16 +59,15 @@ namespace osu.Game.Screens.Menu
background.Preload(game); background.Preload(game);
buttons.OnSettings = game.ToggleOptions; buttons.OnSettings = game.ToggleOptions;
} }
protected override void OnEntering(GameMode last) protected override void OnEntering(Screen last)
{ {
base.OnEntering(last); base.OnEntering(last);
buttons.FadeInFromZero(500); buttons.FadeInFromZero(500);
} }
protected override void OnSuspending(GameMode next) protected override void OnSuspending(Screen next)
{ {
base.OnSuspending(next); base.OnSuspending(next);
@ -80,7 +79,7 @@ namespace osu.Game.Screens.Menu
Content.MoveTo(new Vector2(-800, 0), length, EasingTypes.InSine); Content.MoveTo(new Vector2(-800, 0), length, EasingTypes.InSine);
} }
protected override void OnResuming(GameMode last) protected override void OnResuming(Screen last)
{ {
base.OnResuming(last); base.OnResuming(last);
@ -92,10 +91,10 @@ namespace osu.Game.Screens.Menu
Content.MoveTo(new Vector2(0, 0), length, EasingTypes.OutQuint); Content.MoveTo(new Vector2(0, 0), length, EasingTypes.OutQuint);
} }
protected override bool OnExiting(GameMode next) protected override bool OnExiting(Screen next)
{ {
buttons.State = MenuState.Exit; buttons.State = MenuState.Exit;
Content.FadeOut(ButtonSystem.EXIT_DELAY); Content.FadeOut(3000);
return base.OnExiting(next); return base.OnExiting(next);
} }
} }

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