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.Allocation;
using osu.Framework.Desktop.Platform;
using osu.Framework.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Game;
using osu.Game.Modes;
using osu.Game.Modes.Catch;

View File

@ -5,7 +5,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using osu.Framework;
using osu.Framework.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@ -17,6 +17,7 @@ using osu.Game.Online.Chat;
using OpenTK;
using osu.Framework.Allocation;
using osu.Game.Online.Chat.Drawables;
using osu.Game.Overlays;
namespace osu.Desktop.VisualTests.Tests
{
@ -25,119 +26,16 @@ namespace osu.Desktop.VisualTests.Tests
private ScheduledDelegate messageRequest;
public override string Name => @"Chat";
public override string Description => @"Testing API polling";
FlowContainer flow;
private Scheduler scheduler = new Scheduler();
private APIAccess api;
private ChannelDisplay channelDisplay;
[BackgroundDependencyLoader]
private void load(APIAccess api)
{
this.api = api;
}
public override string Description => @"Testing chat api and overlay";
public override void Reset()
{
base.Reset();
if (api.State != APIState.Online)
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
Add(new ChatOverlay()
{
RelativeSizeAxes = Axes.Both,
Direction = FlowDirections.Vertical
State = Visibility.Visible
});
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
using System.Collections.Generic;
using osu.Framework.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics;
using osu.Framework.MathUtils;
using osu.Framework.Timing;

View File

@ -3,7 +3,7 @@
using System.Collections.Generic;
using osu.Framework;
using osu.Framework.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using OpenTK;
@ -37,7 +37,7 @@ namespace osu.Desktop.VisualTests.Tests
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 };
private Container playfieldContainer;
@ -75,6 +75,7 @@ namespace osu.Desktop.VisualTests.Tests
Length = 400,
Position = new Vector2(-200, 0),
Velocity = 1,
TickDistance = 100,
}));
break;
case HitObjectType.Spinner:

View File

@ -1,7 +1,7 @@
// 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.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using OpenTK.Input;

View File

@ -1,7 +1,7 @@
// 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.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites;
using osu.Game.Screens.Menu;

View File

@ -1,7 +1,7 @@
// 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.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Overlays;

View File

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

View File

@ -1,7 +1,7 @@
// 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.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics;
using osu.Game.Graphics.UserInterface;
using OpenTK.Input;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -1,7 +1,7 @@
// 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.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;

View File

@ -2,7 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework;
using osu.Framework.GameModes.Testing;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics.Cursor;
using osu.Game.Database;
using osu.Game;
@ -21,7 +21,7 @@ namespace osu.Desktop.VisualTests
{
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
// 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 System.Reflection;
using System.Drawing;
using osu.Game.Screens.Menu;
namespace osu.Desktop
{
class OsuGameDesktop : OsuGame
{
private VersionManager versionManager;
public override bool IsDeployedBuild => versionManager.IsDeployedBuild;
public OsuGameDesktop(string[] args = null)
: base(args)
{
versionManager = new VersionManager { Depth = int.MinValue };
}
protected override void 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)

View File

@ -1,9 +1,12 @@
// 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.ComponentModel;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays;
@ -15,13 +18,19 @@ using osu.Framework.Graphics.Textures;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
using System.Net.Http;
using osu.Framework.Logging;
namespace osu.Desktop.Overlays
{
public class VersionManager : OverlayContainer
{
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;
@ -30,7 +39,7 @@ namespace osu.Desktop.Overlays
[BackgroundDependencyLoader]
private void load(NotificationManager notification, OsuColour colours, TextureStore textures)
{
this.notification = notification;
this.notificationManager = notification;
AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
@ -40,14 +49,13 @@ namespace osu.Desktop.Overlays
bool isDebug = false;
Debug.Assert(isDebug = true);
var asm = Assembly.GetEntryAssembly().GetName();
string version;
if (asm.Version.Major == 0)
if (!IsDeployedBuild)
{
version = @"local " + (isDebug ? @"debug" : @"release");
}
else
version = $@"{asm.Version.Major}.{asm.Version.Minor}.{asm.Version.Build}";
version = $@"{assembly.Version.Major}.{assembly.Version.Minor}.{assembly.Version.Build}";
Children = new Drawable[]
{
@ -95,7 +103,8 @@ namespace osu.Desktop.Overlays
}
};
updateChecker();
if (IsDeployedBuild)
updateChecker();
}
protected override void LoadComplete()
@ -110,32 +119,71 @@ namespace osu.Desktop.Overlays
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)
return;
var info = await updateManager.CheckForUpdate();
if (info.ReleasesToApply.Count > 0)
try
{
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..."
};
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);
notification = new UpdateProgressNotification { State = ProgressNotificationState.Active };
Schedule(() => notificationManager.Post(notification));
}
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.
Scheduler.AddDelayed(updateChecker, 60000 * 30);
//likely have no internet connection.
//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;
}
};
[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 Include="System" />
<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" />
</ItemGroup>
<ItemGroup>
@ -219,7 +229,6 @@
<ItemGroup>
<Content Include="lazer.ico" />
</ItemGroup>
<ItemGroup />
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<!-- 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.

View File

@ -5,6 +5,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
-->
<packages>
<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="Splat" version="1.6.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>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Graphics;
using osu.Game.Modes.Catch.UI;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI;
using osu.Game.Modes.UI;
using osu.Game.Beatmaps;
using System;
namespace osu.Game.Modes.Catch
{
@ -25,5 +24,7 @@ namespace osu.Game.Modes.Catch
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
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" />
</ItemGroup>
<ItemGroup>
<Compile Include="CatchDifficultyCalculator.cs" />
<Compile Include="Objects\CatchBaseHit.cs" />
<Compile Include="Objects\CatchConverter.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>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Graphics;
using osu.Game.Modes.Mania.UI;
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.UI;
using osu.Game.Beatmaps;
using System;
namespace osu.Game.Modes.Mania
{
@ -26,5 +24,7 @@ namespace osu.Game.Modes.Mania
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
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" />
</ItemGroup>
<ItemGroup>
<Compile Include="ManiaDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableNote.cs" />
<Compile Include="Objects\HoldNote.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);
OsuJudgementInfo osuJudgement = Judgement as OsuJudgementInfo;
if (hitOffset < hit50)
{
Judgement.Result = HitResult.Hit;
OsuJudgementInfo osuInfo = Judgement as OsuJudgementInfo;
if (hitOffset < hit300)
osuInfo.Score = OsuScoreResult.Hit300;
osuJudgement.Score = OsuScoreResult.Hit300;
else if (hitOffset < hit100)
osuInfo.Score = OsuScoreResult.Hit100;
osuJudgement.Score = OsuScoreResult.Hit100;
else if (hitOffset < hit50)
osuInfo.Score = OsuScoreResult.Hit50;
osuJudgement.Score = OsuScoreResult.Hit50;
}
else
Judgement.Result = HitResult.Miss;

View File

@ -4,7 +4,6 @@
using System.ComponentModel;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Objects.Drawables;
using osu.Framework.Graphics;
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)
{
@ -49,7 +48,37 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
public class OsuJudgementInfo : PositionalJudgementInfo
{
/// <summary>
/// The score the user achieved.
/// </summary>
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;
}
@ -73,5 +102,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Hit100,
[Description(@"300")]
Hit300,
[Description(@"10")]
SliderTick
}
}

View File

@ -1,11 +1,13 @@
// 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.Collections.Generic;
using OpenTK;
using osu.Framework.Graphics;
using osu.Game.Modes.Objects.Drawables;
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
{
@ -17,6 +19,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
private List<ISliderProgress> components = new List<ISliderProgress>();
private Container<DrawableSliderTick> ticks;
SliderBody body;
SliderBall ball;
@ -33,6 +37,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
Position = s.StackedPosition,
PathWidth = s.Scale * 64,
},
ticks = new Container<DrawableSliderTick>(),
bouncer1 = new SliderBouncer(s, false)
{
Position = s.Curve.PositionAt(1),
@ -63,6 +68,26 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
components.Add(ball);
components.Add(bouncer1);
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
@ -82,7 +107,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
if (repeat > currentRepeat)
{
if (ball.Tracking)
if (repeat < slider.RepeatCount && ball.Tracking)
PlaySample();
currentRepeat = repeat;
}
@ -96,7 +121,8 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
if (initialCircle.Judgement?.Result != HitResult.Hit)
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)
@ -106,8 +132,22 @@ namespace osu.Game.Modes.Osu.Objects.Drawables
if (!userTriggered && Time.Current >= HitObject.EndTime)
{
j.Score = sc.Score;
j.Result = sc.Result;
var ticksCount = ticks.Children.Count() + 1;
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

View File

@ -25,7 +25,7 @@ namespace osu.Game.Modes.Osu.Objects.Drawables.Pieces
{
BlendingMode = BlendingMode.Additive,
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.Containers;
using osu.Framework.Graphics.OpenGL.Textures;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Textures;
using osu.Game.Configuration;
using OpenTK;

View File

@ -1,9 +1,12 @@
// 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;
namespace osu.Game.Modes.Osu.Objects
{
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 abstract HitObjectType Type { get; }
public override void SetDefaultsFromBeatmap(Beatmap beatmap)
{
base.SetDefaultsFromBeatmap(beatmap);
@ -36,14 +38,12 @@ namespace osu.Game.Modes.Osu.Objects
[Flags]
public enum HitObjectType
{
Circle = 1,
Slider = 2,
NewCombo = 4,
CircleNewCombo = 5,
SliderNewCombo = 6,
Spinner = 8,
Circle = 1 << 0,
Slider = 1 << 1,
NewCombo = 1 << 2,
Spinner = 1 << 3,
ColourHax = 122,
Hold = 128,
ManiaLong = 128,
Hold = 1 << 7,
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>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Game.Beatmaps;
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
{
@ -43,17 +46,72 @@ namespace osu.Game.Modes.Osu.Objects
}
public double Velocity;
public double TickDistance;
public override void SetDefaultsFromBeatmap(Beatmap 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;
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

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 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 DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new OsuDifficultyCalculator(beatmap);
protected override PlayMode PlayMode => PlayMode.Osu;
}
}

View File

@ -36,24 +36,8 @@ namespace osu.Game.Modes.Osu
foreach (OsuJudgementInfo j in Judgements)
{
switch (j.Score)
{
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;
}
score += j.ScoreValue;
maxScore += j.MaxScoreValue;
}
TotalScore.Value = score;

View File

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

View File

@ -59,14 +59,18 @@
<Compile Include="Objects\Drawables\Pieces\GlowPiece.cs" />
<Compile Include="Objects\Drawables\HitExplosion.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\SliderBouncer.cs" />
<Compile Include="Objects\Drawables\Pieces\SpinnerDisc.cs" />
<Compile Include="Objects\Drawables\Pieces\TrianglesPiece.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBall.cs" />
<Compile Include="Objects\Drawables\Pieces\SliderBody.cs" />
<Compile Include="Objects\OsuHitObjectDifficulty.cs" />
<Compile Include="Objects\OsuHitObjectParser.cs" />
<Compile Include="Objects\SliderCurve.cs" />
<Compile Include="Objects\SliderTick.cs" />
<Compile Include="OsuDifficultyCalculator.cs" />
<Compile Include="OsuScore.cs" />
<Compile Include="OsuScoreProcessor.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
using System;
using System.Collections.Generic;
using osu.Game.Graphics;
using osu.Game.Modes.Objects;
using osu.Game.Modes.Osu.Objects;
using osu.Game.Modes.Osu.UI;
using osu.Game.Modes.Taiko.UI;
using osu.Game.Modes.UI;
@ -26,5 +24,7 @@ namespace osu.Game.Modes.Taiko
public override ScoreProcessor CreateScoreProcessor(int hitObjectCount) => null;
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" />
</ItemGroup>
<ItemGroup>
<Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Objects\Drawable\DrawableTaikoHit.cs" />
<Compile Include="Objects\TaikoBaseHit.cs" />
<Compile Include="Objects\TaikoConverter.cs" />

View File

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

View File

@ -26,26 +26,36 @@ namespace osu.Game.Beatmaps
return 60000 / BeatLengthAt(time);
}
public double BeatLengthAt(double time, bool applyMultipliers = false)
public double BeatLengthAt(double time)
{
int point = 0;
int samplePoint = 0;
ControlPoint overridePoint;
ControlPoint timingPoint = TimingPointAt(time, out overridePoint);
return timingPoint.BeatLength;
}
for (int i = 0; i < ControlPoints.Count; i++)
if (ControlPoints[i].Time <= time)
public ControlPoint TimingPointAt(double time, out ControlPoint overridePoint)
{
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)
point = i;
else
samplePoint = i;
if (controlPoint.TimingChange)
{
timingPoint = controlPoint;
overridePoint = null;
}
else overridePoint = controlPoint;
}
else break;
}
double mult = 1;
if (applyMultipliers && samplePoint > point)
mult = ControlPoints[samplePoint].VelocityAdjustment;
return ControlPoints[point].BeatLength * mult;
return timingPoint ?? ControlPoint.Default;
}
}
}

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)
{
@ -68,6 +68,7 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X,
};
BeatmapSet = beatmap.BeatmapSetInfo;
BeatmapPanels = beatmap.BeatmapSetInfo.Beatmaps.Select(b => new BeatmapPanel(b)
{
Alpha = 0,
@ -76,7 +77,6 @@ namespace osu.Game.Beatmaps.Drawables
RelativeSizeAxes = Axes.X,
}).ToList();
BeatmapSet = set;
Header.AddDifficultyIcons(BeatmapPanels);
}

View File

@ -1,20 +1,21 @@
// 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;
namespace osu.Game.Beatmaps.Timing
{
public class ControlPoint
{
public static ControlPoint Default = new ControlPoint
{
BeatLength = 500,
TimingChange = true,
};
public double Time;
public double BeatLength;
public double VelocityAdjustment;
public bool TimingChange;
}
internal enum TimeSignatures

View File

@ -1,12 +1,6 @@
// 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;
namespace osu.Game.Beatmaps.Timing
{
class TimingChange : ControlPoint

View File

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

View File

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

View File

@ -8,6 +8,7 @@ using osu.Game.Modes;
using osu.Game.Screens.Play;
using SQLite.Net.Attributes;
using SQLiteNetExtensions.Attributes;
using osu.Game.Beatmaps;
namespace osu.Game.Database
{
@ -73,7 +74,23 @@ namespace osu.Game.Database
// Metadata
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)
{

View File

@ -8,6 +8,8 @@ using OpenTK;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Transformations;
using osu.Game.Configuration;
using osu.Framework.Configuration;
namespace osu.Game.Graphics.Containers
{
@ -15,6 +17,8 @@ namespace osu.Game.Graphics.Containers
{
public float ParallaxAmount = 0.02f;
private Bindable<bool> parallaxEnabled;
public override bool Contains(Vector2 screenSpacePos) => true;
public ParallaxContainer()
@ -34,9 +38,18 @@ namespace osu.Game.Graphics.Containers
protected override Container<Drawable> Content => content;
[BackgroundDependencyLoader]
private void load(UserInputManager input)
private void load(UserInputManager input, OsuConfigManager config)
{
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;
@ -45,9 +58,12 @@ namespace osu.Game.Graphics.Containers
{
base.Update();
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);
if (parallaxEnabled)
{
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;
}

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 SpriteText labelSpriteText;
private AudioSample sampleChecked;
private AudioSample sampleUnchecked;
private SampleChannel sampleChecked;
private SampleChannel sampleUnchecked;
public OsuCheckbox()
{

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using osu.Framework;
@ -23,8 +24,6 @@ namespace osu.Game.Modes.Objects.Drawables
public bool Interactive = true;
public Container<DrawableHitObject> ChildObjects;
public JudgementInfo Judgement;
public abstract JudgementInfo CreateJudgementInfo();
@ -55,7 +54,7 @@ namespace osu.Game.Modes.Objects.Drawables
}
}
AudioSample sample;
SampleChannel sample;
[BackgroundDependencyLoader]
private void load(AudioManager audio)
@ -66,7 +65,7 @@ namespace osu.Game.Modes.Objects.Drawables
sample = audio.Sample.Get($@"Gameplay/{sampleSet}-hit{hitType}");
}
protected void PlaySample()
protected virtual void PlaySample()
{
sample?.Play();
}
@ -85,6 +84,19 @@ namespace osu.Game.Modes.Objects.Drawables
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>
/// Process a hit of this hitobject. Carries out judgement.
/// </summary>
@ -119,7 +131,11 @@ namespace osu.Game.Modes.Objects.Drawables
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()

View File

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

View File

@ -1,23 +1,29 @@
// 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 OpenTK.Graphics;
using System;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Transformations;
using System;
using osu.Game.Graphics;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Modes.UI
{
public class HealthDisplay : Container
{
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()
{
@ -26,19 +32,38 @@ namespace osu.Game.Modes.UI
background = new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.Gray,
Colour = Color4.Black,
},
fill = new Box
fill = new Container
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
Scale = new Vector2(0, 1),
},
Masking = true,
Children = new[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
}
}
},
};
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)
{
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 virtual HealthDisplay CreateHealthDisplay() => new HealthDisplay
{
Size = new Vector2(0.5f, 20),
Size = new Vector2(1, 5),
RelativeSizeAxes = Axes.X,
Padding = new MarginPadding(5)
Margin = new MarginPadding { Top = 20 }
};
public virtual void OnHit(HitObject h)

View File

@ -196,7 +196,10 @@ namespace osu.Game.Online.API
Logger.Log($@"Performing request {req}", LoggingTarget.Network);
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;
return true;
}

View File

@ -3,12 +3,7 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Configuration;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
namespace osu.Game.Online.Chat
{
@ -30,16 +25,16 @@ namespace osu.Game.Online.Chat
//internal bool Joined;
public const int MAX_HISTORY = 100;
public const int MAX_HISTORY = 300;
[JsonConstructor]
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);
purgeOldMessages();

View File

@ -1,10 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
@ -15,6 +17,50 @@ namespace osu.Game.Online.Chat.Drawables
{
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 text_size = 20;
@ -25,6 +71,8 @@ namespace osu.Game.Online.Chat.Drawables
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Padding = new MarginPadding { Left = 15, Right = 15 };
Children = new Drawable[]
{
new Container
@ -34,13 +82,19 @@ namespace osu.Game.Online.Chat.Drawables
{
new OsuSpriteText
{
Text = Message.Timestamp.LocalDateTime.ToLongTimeString(),
TextSize = text_size,
Colour = Color4.Gray
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = @"Exo2.0-SemiBold",
Text = $@"{Message.Timestamp.LocalDateTime:hh:mm:ss}",
FixedWidth = true,
TextSize = text_size * 0.75f,
Alpha = 0.4f,
},
new OsuSpriteText
{
Text = Message.User.Name,
Font = @"Exo2.0-BoldItalic",
Text = $@"{Message.User.Name}:",
Colour = getUsernameColour(Message),
TextSize = text_size,
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
@ -51,7 +105,7 @@ namespace osu.Game.Online.Chat.Drawables
{
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Left = padding + 10 },
Padding = new MarginPadding { Left = padding + 15 },
Children = new Drawable[]
{
new OsuSpriteText

View File

@ -4,25 +4,25 @@
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Primitives;
using osu.Framework.MathUtils;
using osu.Framework.Threading;
using osu.Game.Graphics.Sprites;
using OpenTK;
namespace osu.Game.Online.Chat.Drawables
{
public class ChannelDisplay : Container
public class DrawableChannel : Container
{
private readonly Channel channel;
private FlowContainer flow;
private ScrollContainer scroll;
public ChannelDisplay(Channel channel)
public DrawableChannel(Channel channel)
{
this.channel = channel;
newMessages(channel.Messages);
channel.NewMessagesArrived += newMessages;
RelativeSizeAxes = Axes.Both;
@ -36,8 +36,9 @@ namespace osu.Game.Online.Chat.Drawables
Anchor = Anchor.Centre,
Origin = Anchor.Centre
},
new ScrollContainer
scroll = new ScrollContainer
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
flow = new FlowContainer
@ -45,37 +46,54 @@ namespace osu.Game.Online.Chat.Drawables
Direction = FlowDirections.Vertical,
RelativeSizeAxes = Axes.X,
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)
{
base.Dispose(isDisposing);
channel.NewMessagesArrived -= newMessages;
channel.NewMessagesArrived -= newMessagesArrived;
}
[BackgroundDependencyLoader]
private void load()
{
newMessages(channel.Messages);
}
private void newMessages(IEnumerable<Message> newMessages)
private void newMessagesArrived(IEnumerable<Message> newMessages)
{
if (!IsLoaded) return;
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
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)
flow.Remove(flow.Children.First());
while (flow.Children.Count(c => c.LifetimeEnd == double.MaxValue) > Channel.MAX_HISTORY)
{
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")]
public long Id;
//todo: this should be inside sender.
[JsonProperty(@"user_id")]
public int UserId;

View File

@ -3,7 +3,7 @@
using System;
using osu.Framework.Configuration;
using osu.Framework.GameModes;
using osu.Framework.Screens;
using osu.Game.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -31,6 +31,8 @@ namespace osu.Game
{
public class OsuGame : OsuGameBase
{
public virtual bool IsDeployedBuild => false;
public Toolbar Toolbar;
private ChatOverlay chat;
@ -39,10 +41,18 @@ namespace osu.Game
private NotificationManager notificationManager;
private MainMenu mainMenu => modeStack?.ChildGameMode as MainMenu;
private Intro intro => modeStack as Intro;
private Intro 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;
@ -106,11 +116,11 @@ namespace osu.Game
}
});
(modeStack = new Intro()).Preload(this, d =>
(screenStack = new Loader()).Preload(this, d =>
{
modeStack.ModePushed += modeAdded;
modeStack.Exited += modeRemoved;
mainContent.Add(modeStack);
screenStack.ModePushed += screenAdded;
screenStack.Exited += screenRemoved;
mainContent.Add(screenStack);
});
//overlay elements
@ -148,7 +158,7 @@ namespace osu.Game
(Toolbar = new Toolbar
{
Depth = -3,
OnHome = delegate { mainMenu?.MakeCurrent(); },
OnHome = delegate { intro?.ChildScreen?.MakeCurrent(); },
OnPlayModeChange = delegate (PlayMode m) { PlayMode.Value = m; },
}).Preload(this, t =>
{
@ -206,20 +216,16 @@ namespace osu.Game
return base.OnKeyDown(state, args);
}
public Action<GameMode> ModeChanged;
public event Action<Screen> ModeChanged;
private Container mainContent;
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.
if ((newMode as OsuGameMode)?.ShowOverlays != true)
if ((newScreen as OsuScreen)?.ShowOverlays != true)
{
Toolbar.State = Visibility.Hidden;
musicController.State = Visibility.Hidden;
@ -230,22 +236,24 @@ namespace osu.Game
Toolbar.State = Visibility.Visible;
}
Cursor.FadeIn(100);
if (newScreen is MainMenu)
Cursor.FadeIn(100);
ModeChanged?.Invoke(newMode);
ModeChanged?.Invoke(newScreen);
if (newMode == null)
Host.Exit();
if (newScreen == null)
Exit();
}
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
{
intro.MakeCurrent();
});
Scheduler.Add(intro.MakeCurrent);
return true;
}
@ -256,21 +264,21 @@ namespace osu.Game
{
base.UpdateAfterChildren();
if (modeStack.ChildGameMode != null)
modeStack.ChildGameMode.Padding = new MarginPadding { Top = Toolbar.Position.Y + Toolbar.DrawHeight };
if (intro?.ChildScreen != null)
intro.ChildScreen.Padding = new MarginPadding { Top = Toolbar.Position.Y + Toolbar.DrawHeight };
}
private void modeAdded(GameMode newMode)
private void screenAdded(Screen newScreen)
{
newMode.ModePushed += modeAdded;
newMode.Exited += modeRemoved;
newScreen.ModePushed += screenAdded;
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.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -19,12 +18,19 @@ using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
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
{
public class ChatOverlay : OverlayContainer, IOnlineComponent
public class ChatOverlay : FocusedOverlayContainer, IOnlineComponent
{
private ChannelDisplay channelDisplay;
const float textbox_height = 40;
private DrawableChannel channelDisplay;
private ScheduledDelegate messageRequest;
@ -32,6 +38,8 @@ namespace osu.Game.Overlays
protected override Container<Drawable> Content => content;
private FocusedTextBox inputTextBox;
private APIAccess api;
public ChatOverlay()
@ -47,15 +55,65 @@ namespace osu.Game.Overlays
{
Depth = float.MaxValue,
RelativeSizeAxes = Axes.Both,
Colour = OsuColour.Gray(0.1f).Opacity(0.4f),
Colour = Color4.Black,
Alpha = 0.9f,
},
content = new Container
{
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]
private void load(APIAccess api)
{
@ -69,7 +127,7 @@ namespace osu.Game.Overlays
private void addChannel(Channel channel)
{
Add(channelDisplay = new ChannelDisplay(channel));
Add(channelDisplay = new DrawableChannel(channel));
careChannels.Add(channel);
}
@ -82,10 +140,11 @@ namespace osu.Game.Overlays
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);
}
var ids = messages.Select(m => m.ChannelId).Distinct();
//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;
@ -151,6 +210,8 @@ namespace osu.Game.Overlays
ListChannelsRequest req = new ListChannelsRequest();
req.Success += delegate (List<Channel> channels)
{
Debug.Assert(careChannels.Count == 0);
Scheduler.Add(delegate
{
loading.FadeOut(100);

View File

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

View File

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

View File

@ -1,7 +1,11 @@
// 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.Game.Graphics;
using osu.Framework.Graphics.Colour;
using OpenTK.Graphics;
namespace osu.Game.Overlays.Notifications
{
@ -14,5 +18,11 @@ namespace osu.Game.Overlays.Notifications
this.progressNotification = progressNotification;
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 TextAwesome iconDrawable;
protected Box IconBackgound;
public SimpleNotification()
{
IconContent.Add(new Drawable[]
{
new Box
IconBackgound = new Box
{
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
{
Anchor = Anchor.Centre,
Icon = icon ,
Icon = icon,
}
});

View File

@ -74,6 +74,7 @@ namespace osu.Game.Overlays.Options
{
Content.Anchor = 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 Box leftGlow, centerGlow, rightGlow;

View File

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

View File

@ -5,7 +5,7 @@ using System;
using System.Threading;
using osu.Framework;
using osu.Framework.Allocation;
using osu.Framework.GameModes;
using osu.Framework.Screens;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transformations;
using osu.Framework.Input;
@ -13,9 +13,9 @@ using OpenTK;
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();
}
@ -37,21 +37,21 @@ namespace osu.Game.Screens
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
// 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;
}
// 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);
base.Push(mode);
base.Push(screen);
return true;
}
@ -62,7 +62,7 @@ namespace osu.Game.Screens
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.MoveToX(x_movement_amount);
@ -73,13 +73,13 @@ namespace osu.Game.Screens
base.OnEntering(last);
}
protected override void OnSuspending(GameMode next)
protected override void OnSuspending(Screen next)
{
Content.MoveToX(-x_movement_amount, transition_length, EasingTypes.InOutQuart);
base.OnSuspending(next);
}
protected override bool OnExiting(GameMode next)
protected override bool OnExiting(Screen next)
{
Content.FadeOut(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);
}
protected override void OnResuming(GameMode last)
protected override void OnResuming(Screen last)
{
Content.MoveToX(0, transition_length, EasingTypes.OutExpo);
base.OnResuming(last);

View File

@ -9,7 +9,7 @@ using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Screens.Backgrounds
{
public class BackgroundModeBeatmap : BackgroundMode
public class BackgroundScreenBeatmap : BackgroundScreen
{
private Background background;
@ -55,7 +55,7 @@ namespace osu.Game.Screens.Backgrounds
}
}
public BackgroundModeBeatmap(WorkingBeatmap beatmap)
public BackgroundScreenBeatmap(WorkingBeatmap beatmap)
{
Beatmap = beatmap;
}
@ -66,9 +66,9 @@ namespace osu.Game.Screens.Backgrounds
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

View File

@ -5,19 +5,19 @@ using osu.Game.Graphics.Backgrounds;
namespace osu.Game.Screens.Backgrounds
{
public class BackgroundModeCustom : BackgroundMode
public class BackgroundScreenCustom : BackgroundScreen
{
private readonly string textureName;
public BackgroundModeCustom(string textureName)
public BackgroundScreenCustom(string textureName)
{
this.textureName = 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
{
public class BackgroundModeDefault : BackgroundMode
public class BackgroundScreenDefault : BackgroundScreen
{
[BackgroundDependencyLoader]
private void load(BaseGame game)

View File

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

View File

@ -3,7 +3,7 @@
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
{
class ChartListing : GameModeWhiteBox
class ChartListing : ScreenWhiteBox
{
protected override IEnumerable<Type> PossibleChildren => new[] {
typeof(ChartInfo)

View File

@ -3,7 +3,7 @@
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>.
// 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 OpenTK.Graphics;
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);
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));
return base.OnExiting(next);

View File

@ -3,7 +3,7 @@
using System;
using System.Collections.Generic;
using osu.Framework.GameModes;
using osu.Framework.Screens;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@ -17,7 +17,7 @@ using OpenTK.Graphics;
namespace osu.Game.Screens
{
public class GameModeWhiteBox : OsuGameMode
public class ScreenWhiteBox : OsuScreen
{
private BackButton popButton;
@ -29,9 +29,9 @@ namespace osu.Game.Screens
private Container textContainer;
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);
@ -54,7 +54,7 @@ namespace osu.Game.Screens
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);
Content.FadeOut(transition_time, EasingTypes.OutExpo);
@ -62,7 +62,7 @@ namespace osu.Game.Screens
return base.OnExiting(next);
}
protected override void OnSuspending(GameMode next)
protected override void OnSuspending(Screen next)
{
base.OnSuspending(next);
@ -70,7 +70,7 @@ namespace osu.Game.Screens
Content.FadeOut(transition_time, EasingTypes.OutExpo);
}
protected override void OnResuming(GameMode last)
protected override void OnResuming(Screen last)
{
base.OnResuming(last);
@ -78,7 +78,7 @@ namespace osu.Game.Screens
Content.FadeIn(transition_time, EasingTypes.OutExpo);
}
public GameModeWhiteBox()
public ScreenWhiteBox()
{
Children = new Drawable[]
{
@ -148,7 +148,7 @@ namespace osu.Game.Screens
BackgroundColour = getColourFor(t),
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 Key triggerKey;
private string text;
private AudioSample sampleClick;
private SampleChannel sampleClick;
public override bool Contains(Vector2 screenSpacePos)
{

View File

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

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.Sample;
using osu.Framework.Audio.Track;
using osu.Framework.GameModes;
using osu.Framework.Screens;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transformations;
using osu.Game.Graphics.Containers;
@ -14,7 +14,7 @@ using OpenTK.Graphics;
namespace osu.Game.Screens.Menu
{
class Intro : OsuGameMode
public class Intro : OsuScreen
{
private OsuLogo logo;
@ -24,13 +24,13 @@ namespace osu.Game.Screens.Menu
internal bool DidLoadMenu;
MainMenu mainMenu;
private AudioSample welcome;
private AudioSample seeya;
private AudioTrack bgm;
private SampleChannel welcome;
private SampleChannel seeya;
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()
{
@ -65,7 +65,7 @@ namespace osu.Game.Screens.Menu
bgm.Looping = true;
}
protected override void OnEntering(GameMode last)
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
@ -77,8 +77,7 @@ namespace osu.Game.Screens.Menu
{
bgm.Start();
mainMenu = new MainMenu();
mainMenu.Preload(Game);
(mainMenu = new MainMenu()).Preload(Game);
Scheduler.AddDelayed(delegate
{
@ -95,24 +94,27 @@ namespace osu.Game.Screens.Menu
logo.FadeIn(20000, EasingTypes.OutQuint);
}
protected override void OnSuspending(GameMode next)
protected override void OnSuspending(Screen next)
{
Content.FadeOut(300);
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.
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.
seeya.Play();
double fadeOutTime = (last.LifetimeEnd - Time.Current) + 100;
double fadeOutTime = 2000;
Scheduler.AddDelayed(Exit, fadeOutTime);

View File

@ -2,8 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.GameModes;
using osu.Framework.GameModes.Testing;
using osu.Framework.Screens;
using osu.Framework.Screens.Testing;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Transformations;
using osu.Game.Graphics.Containers;
@ -16,20 +16,20 @@ using osu.Game.Screens.Select;
namespace osu.Game.Screens.Menu
{
public class MainMenu : OsuGameMode
public class MainMenu : OsuScreen
{
private ButtonSystem buttons;
public override string Name => @"Main Menu";
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()
{
background = new BackgroundModeDefault();
background = new BackgroundScreenDefault();
Children = new Drawable[]
{
@ -59,16 +59,15 @@ namespace osu.Game.Screens.Menu
background.Preload(game);
buttons.OnSettings = game.ToggleOptions;
}
protected override void OnEntering(GameMode last)
protected override void OnEntering(Screen last)
{
base.OnEntering(last);
buttons.FadeInFromZero(500);
}
protected override void OnSuspending(GameMode next)
protected override void OnSuspending(Screen next)
{
base.OnSuspending(next);
@ -80,7 +79,7 @@ namespace osu.Game.Screens.Menu
Content.MoveTo(new Vector2(-800, 0), length, EasingTypes.InSine);
}
protected override void OnResuming(GameMode last)
protected override void OnResuming(Screen last)
{
base.OnResuming(last);
@ -92,10 +91,10 @@ namespace osu.Game.Screens.Menu
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;
Content.FadeOut(ButtonSystem.EXIT_DELAY);
Content.FadeOut(3000);
return base.OnExiting(next);
}
}

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