1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-05 12:32:58 +08:00

Merge branch 'master' into repeat-points

This commit is contained in:
ColdVolcano 2018-01-31 21:30:28 -06:00
commit 06b615c474
67 changed files with 1167 additions and 227 deletions

@ -1 +1 @@
Subproject commit a7dd1fba8473f636ee8f6e49075331d9076383ee Subproject commit 90bf49a2df3dbad5994d922df63e4891c622dbc3

@ -1 +1 @@
Subproject commit 7724abdf1d7c9705ba2e3989a9c604e17ccdc871 Subproject commit 266965f0d795b94a126e2da302bd2c10eadd642a

View File

@ -32,6 +32,10 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="JetBrains.Annotations, Version=11.1.0.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="JetBrains.Annotations" version="11.1.0" targetFramework="net461" />
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages> </packages>

View File

@ -0,0 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration.Tracking;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
namespace osu.Game.Rulesets.Mania.Configuration
{
public class ManiaConfigManager : RulesetConfigManager<ManiaSetting>
{
public ManiaConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant)
: base(settings, ruleset, variant)
{
}
protected override void InitialiseDefaults()
{
base.InitialiseDefaults();
Set(ManiaSetting.ScrollTime, 1500.0, 50.0, 10000.0, 50.0);
}
public override TrackedSettings CreateTrackedSettings() => new TrackedSettings
{
new TrackedSetting<double>(ManiaSetting.ScrollTime, v => new SettingDescription(v, "Scroll Time", $"{v}ms"))
};
}
public enum ManiaSetting
{
ScrollTime
}
}

View File

@ -11,13 +11,14 @@ using osu.Framework.Graphics.Colour;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using System; using System;
using System.Linq;
using osu.Framework.Input.Bindings; using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
{ {
public class Column : ScrollingPlayfield, IHasAccentColour public class Column : ScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{ {
private const float key_icon_size = 10; private const float key_icon_size = 10;
private const float key_icon_corner_radius = 3; private const float key_icon_corner_radius = 3;
@ -259,5 +260,26 @@ namespace osu.Game.Rulesets.Mania.UI
public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false; public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false;
public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false; public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false;
} }
public bool OnPressed(ManiaAction action)
{
// Play the sounds of the next hitobject
if (HitObjects.AliveObjects.Any())
{
// If there are alive hitobjects, we can abuse the fact that AliveObjects are sorted by time (see: Add())
HitObjects.AliveObjects.First().PlaySamples();
}
else
{
// If not, we do a slow search - we might want to do a BinarySearch here if this becomes problematic
// We fallback to LastOrDefault() if we're beyond the last note in the map
var hitObject = HitObjects.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ?? HitObjects.Objects.LastOrDefault();
hitObject?.PlaySamples();
}
return false;
}
public bool OnReleased(ManiaAction action) => false;
} }
} }

View File

@ -7,10 +7,12 @@ using osu.Framework.Graphics.Containers;
using System; using System;
using System.Collections.Generic; using System.Collections.Generic;
using System.Linq; using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Game.Rulesets.Judgements; using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables; using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.UI.Scrolling; using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI namespace osu.Game.Rulesets.Mania.UI
@ -78,6 +80,12 @@ namespace osu.Game.Rulesets.Mania.UI
return null; return null;
} }
[BackgroundDependencyLoader]
private void load(ManiaConfigManager maniaConfig)
{
maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange);
}
internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement) internal void OnJudgement(DrawableHitObject judgedObject, Judgement judgement)
{ {
getStageByColumn(((ManiaHitObject)judgedObject.HitObject).Column).OnJudgement(judgedObject, judgement); getStageByColumn(((ManiaHitObject)judgedObject.HitObject).Column).OnJudgement(judgedObject, judgement);

View File

@ -10,8 +10,11 @@ using osu.Framework.Input;
using osu.Framework.MathUtils; using osu.Framework.MathUtils;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints; using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Mania.Beatmaps; using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Mods; using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects; using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables; using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Replays; using osu.Game.Rulesets.Mania.Replays;
@ -77,11 +80,9 @@ namespace osu.Game.Rulesets.Mania.UI
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this); public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
public override PassThroughInputManager CreateInputManager() public override int Variant => (int)(Mods.OfType<IPlayfieldTypeMod>().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single) + Beatmap.TotalColumns;
{
var variantType = Mods.OfType<IPlayfieldTypeMod>().FirstOrDefault()?.PlayfieldType ?? PlayfieldType.Single; public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, Variant);
return new ManiaInputManager(Ruleset.RulesetInfo, (int)variantType + Beatmap.TotalColumns);
}
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(IsForCurrentRuleset, WorkingBeatmap.Beatmap); protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(IsForCurrentRuleset, WorkingBeatmap.Beatmap);
@ -103,5 +104,7 @@ namespace osu.Game.Rulesets.Mania.UI
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f); protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(1, 0.8f);
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this); protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new ManiaFramedReplayInputHandler(replay, this);
protected override IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => new ManiaConfigManager(settings, Ruleset.RulesetInfo, Variant);
} }
} }

View File

@ -32,6 +32,10 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="JetBrains.Annotations, Version=11.1.0.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -58,6 +62,7 @@
<Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" /> <Compile Include="Beatmaps\Patterns\Legacy\PatternType.cs" />
<Compile Include="Beatmaps\ManiaBeatmapConverter.cs" /> <Compile Include="Beatmaps\ManiaBeatmapConverter.cs" />
<Compile Include="Beatmaps\Patterns\Pattern.cs" /> <Compile Include="Beatmaps\Patterns\Pattern.cs" />
<Compile Include="Configuration\ManiaConfigManager.cs" />
<Compile Include="MathUtils\FastRandom.cs" /> <Compile Include="MathUtils\FastRandom.cs" />
<Compile Include="Judgements\HitWindows.cs" /> <Compile Include="Judgements\HitWindows.cs" />
<Compile Include="Judgements\HoldNoteTailJudgement.cs" /> <Compile Include="Judgements\HoldNoteTailJudgement.cs" />

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="JetBrains.Annotations" version="11.1.0" targetFramework="net461" />
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages> </packages>

View File

@ -56,7 +56,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
continue; continue;
double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime; double endTime = (stackBaseObject as IHasEndTime)?.EndTime ?? stackBaseObject.StartTime;
float stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; double stackThreshold = objectN.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
if (objectN.StartTime - endTime > stackThreshold) if (objectN.StartTime - endTime > stackThreshold)
//We are no longer within stacking range of the next object. //We are no longer within stacking range of the next object.
@ -99,7 +99,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
OsuHitObject objectI = beatmap.HitObjects[i]; OsuHitObject objectI = beatmap.HitObjects[i];
if (objectI.StackHeight != 0 || objectI is Spinner) continue; if (objectI.StackHeight != 0 || objectI is Spinner) continue;
float stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f; double stackThreshold = objectI.TimePreempt * beatmap.BeatmapInfo?.StackLeniency ?? 0.7f;
/* If this object is a hitcircle, then we enter this "special" case. /* If this object is a hitcircle, then we enter this "special" case.
* It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider. * It either ends with a stack of hitcircles only, or a stack of hitcircles that are underneath a slider.

View File

@ -16,7 +16,7 @@ namespace osu.Game.Rulesets.Osu.Mods
public override string Description => @"Play with no approach circles and fading notes for a slight score advantage."; public override string Description => @"Play with no approach circles and fading notes for a slight score advantage.";
public override double ScoreMultiplier => 1.06; public override double ScoreMultiplier => 1.06;
private const float fade_in_duration_multiplier = 0.4f; private const double fade_in_duration_multiplier = 0.4;
private const double fade_out_duration_multiplier = 0.3; private const double fade_out_duration_multiplier = 0.3;
public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables) public void ApplyToDrawableHitObjects(IEnumerable<DrawableHitObject> drawables)

View File

@ -22,8 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
/// </summary> /// </summary>
private bool isRepeatAtEnd => repeatPoint.RepeatIndex % 2 == 0; private bool isRepeatAtEnd => repeatPoint.RepeatIndex % 2 == 0;
public double FadeInTime; private double animDuration;
public double FadeOutTime;
public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider) public DrawableRepeatPoint(RepeatPoint repeatPoint, DrawableSlider drawableSlider)
: base(repeatPoint) : base(repeatPoint)
@ -54,11 +53,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected override void UpdatePreemptState() protected override void UpdatePreemptState()
{ {
var animIn = Math.Min(150, repeatPoint.StartTime - FadeInTime); animDuration = Math.Min(150, repeatPoint.SpanDuration / 2);
this.FadeIn(animIn).ScaleTo(1.2f, animIn) this.FadeIn(animDuration).ScaleTo(1.2f, animDuration / 2)
.Then() .Then()
.ScaleTo(1, 150, Easing.Out); .ScaleTo(1, animDuration / 2, Easing.Out);
} }
protected override void UpdateCurrentState(ArmedState state) protected override void UpdateCurrentState(ArmedState state)
@ -66,14 +65,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
switch (state) switch (state)
{ {
case ArmedState.Idle: case ArmedState.Idle:
this.Delay(FadeOutTime - repeatPoint.StartTime).FadeOut(); this.Delay(HitObject.TimePreempt).FadeOut();
break; break;
case ArmedState.Miss: case ArmedState.Miss:
this.FadeOut(160); this.FadeOut(animDuration);
break; break;
case ArmedState.Hit: case ArmedState.Hit:
this.FadeOut(120, Easing.OutQuint) this.FadeOut(animDuration, Easing.OutQuint)
.ScaleTo(Scale * 1.5f, 120, Easing.OutQuint); .ScaleTo(Scale * 1.5f, animDuration, Easing.OutQuint);
break; break;
} }
} }

View File

@ -75,12 +75,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AddNested(InitialCircle); AddNested(InitialCircle);
var spanDuration = s.Curve.Distance / s.Velocity;
foreach (var tick in s.NestedHitObjects.OfType<SliderTick>()) foreach (var tick in s.NestedHitObjects.OfType<SliderTick>())
{ {
var spanStartTime = s.StartTime + tick.SpanIndex * spanDuration; var spanStartTime = s.StartTime + tick.SpanIndex * s.SpanDuration;
var fadeInTime = spanStartTime + (tick.StartTime - spanStartTime) / 2 - (tick.SpanIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2); var fadeInTime = spanStartTime + (tick.StartTime - spanStartTime) / 2 - (tick.SpanIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2);
var fadeOutTime = spanStartTime + spanDuration; var fadeOutTime = spanStartTime + s.SpanDuration;
var drawableTick = new DrawableSliderTick(tick) var drawableTick = new DrawableSliderTick(tick)
{ {
@ -95,15 +94,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>()) foreach (var repeatPoint in s.NestedHitObjects.OfType<RepeatPoint>())
{ {
var repeatStartTime = s.StartTime + (repeatPoint.RepeatIndex + 1) * spanDuration;
var fadeInTime = repeatStartTime + (repeatPoint.StartTime - repeatStartTime) / 2 - (repeatPoint.RepeatIndex == 0 ? HitObject.TimeFadein : HitObject.TimeFadein / 2);
var fadeOutTime = repeatStartTime + spanDuration;
var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this) var drawableRepeatPoint = new DrawableRepeatPoint(repeatPoint, this)
{ {
FadeInTime = fadeInTime, Position = repeatPoint.Position
FadeOutTime = fadeOutTime,
Position = repeatPoint.Position,
}; };
repeatPoints.Add(drawableRepeatPoint); repeatPoints.Add(drawableRepeatPoint);

View File

@ -20,8 +20,8 @@ namespace osu.Game.Rulesets.Osu.Objects
public double HitWindow100 = 80; public double HitWindow100 = 80;
public double HitWindow300 = 30; public double HitWindow300 = 30;
public float TimePreempt = 600; public double TimePreempt = 600;
public float TimeFadein = 400; public double TimeFadein = 400;
public Vector2 Position { get; set; } public Vector2 Position { get; set; }
public float X => Position.X; public float X => Position.X;

View File

@ -1,10 +1,25 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
namespace osu.Game.Rulesets.Osu.Objects namespace osu.Game.Rulesets.Osu.Objects
{ {
public class RepeatPoint : OsuHitObject public class RepeatPoint : OsuHitObject
{ {
public int RepeatIndex { get; set; } public int RepeatIndex { get; set; }
public double SpanDuration { get; set; }
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
// We want to show the first RepeatPoint as the TimePreempt dictates but on short (and possibly fast) sliders
// we may need to cut down this time on following RepeatPoints to only show up to two RepeatPoints at any given time.
if (RepeatIndex > 0)
TimePreempt = Math.Min(SpanDuration * 2, TimePreempt);
}
} }
} }

View File

@ -60,6 +60,11 @@ namespace osu.Game.Rulesets.Osu.Objects
public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>(); public List<List<SampleInfo>> RepeatSamples { get; set; } = new List<List<SampleInfo>>();
public int RepeatCount { get; set; } public int RepeatCount { get; set; }
/// <summary>
/// The length of one span of this <see cref="Slider"/>.
/// </summary>
public double SpanDuration => Duration / this.SpanCount();
private int stackHeight; private int stackHeight;
public override int StackHeight public override int StackHeight
@ -102,13 +107,12 @@ namespace osu.Game.Rulesets.Osu.Objects
var length = Curve.Distance; var length = Curve.Distance;
var tickDistance = Math.Min(TickDistance, length); var tickDistance = Math.Min(TickDistance, length);
var spanDuration = length / Velocity;
var minDistanceFromEnd = Velocity * 0.01; var minDistanceFromEnd = Velocity * 0.01;
for (var span = 0; span < this.SpanCount(); span++) for (var span = 0; span < this.SpanCount(); span++)
{ {
var spanStartTime = StartTime + span * spanDuration; var spanStartTime = StartTime + span * SpanDuration;
var reversed = span % 2 == 1; var reversed = span % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance) for (var d = tickDistance; d <= length; d += tickDistance)
@ -133,7 +137,7 @@ namespace osu.Game.Rulesets.Osu.Objects
AddNested(new SliderTick AddNested(new SliderTick
{ {
SpanIndex = span, SpanIndex = span,
StartTime = spanStartTime + timeProgress * spanDuration, StartTime = spanStartTime + timeProgress * SpanDuration,
Position = Curve.PositionAt(distanceProgress), Position = Curve.PositionAt(distanceProgress),
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,
@ -146,16 +150,13 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createRepeatPoints() private void createRepeatPoints()
{ {
var repeatDuration = Distance / Velocity;
for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++) for (int repeatIndex = 0, repeat = 1; repeatIndex < RepeatCount; repeatIndex++, repeat++)
{ {
var repeatStartTime = StartTime + repeat * repeatDuration;
AddNested(new RepeatPoint AddNested(new RepeatPoint
{ {
RepeatIndex = repeatIndex, RepeatIndex = repeatIndex,
StartTime = repeatStartTime, SpanDuration = SpanDuration,
StartTime = StartTime + repeat * SpanDuration,
Position = Curve.PositionAt(repeat % 2), Position = Curve.PositionAt(repeat % 2),
StackHeight = StackHeight, StackHeight = StackHeight,
Scale = Scale, Scale = Scale,

View File

@ -63,6 +63,7 @@ namespace osu.Game.Rulesets.Osu.Tests
AddStep("Fast Short Slider", () => testShortHighSpeed()); AddStep("Fast Short Slider", () => testShortHighSpeed());
AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1)); AddStep("Fast Short Slider 1 Repeat", () => testShortHighSpeed(1));
AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2)); AddStep("Fast Short Slider 2 Repeats", () => testShortHighSpeed(2));
AddStep("Fast Short Slider 6 Repeats", () => testShortHighSpeed(6));
AddStep("Perfect Curve", () => testPerfect()); AddStep("Perfect Curve", () => testPerfect());
AddStep("Perfect Curve 1 Repeat", () => testPerfect(1)); AddStep("Perfect Curve 1 Repeat", () => testPerfect(1));

View File

@ -33,6 +33,10 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="JetBrains.Annotations, Version=11.1.0.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="JetBrains.Annotations" version="11.1.0" targetFramework="net461" />
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages> </packages>

View File

@ -35,8 +35,8 @@ namespace osu.Game.Rulesets.Taiko.Replays
{ {
bool hitButton = true; bool hitButton = true;
Frames.Add(new ReplayFrame(-100000, null, null, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(-100000, ReplayButtonState.None));
Frames.Add(new ReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, null, null, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(Beatmap.HitObjects[0].StartTime - 1000, ReplayButtonState.None));
for (int i = 0; i < Beatmap.HitObjects.Count; i++) for (int i = 0; i < Beatmap.HitObjects.Count; i++)
{ {
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
break; break;
} }
Frames.Add(new ReplayFrame(j, null, null, button)); Frames.Add(new TaikoReplayFrame(j, button));
d = (d + 1) % 4; d = (d + 1) % 4;
if (++count == req) if (++count == req)
break; break;
@ -86,7 +86,7 @@ namespace osu.Game.Rulesets.Taiko.Replays
{ {
foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>()) foreach (var tick in drumRoll.NestedHitObjects.OfType<DrumRollTick>())
{ {
Frames.Add(new ReplayFrame(tick.StartTime, null, null, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2)); Frames.Add(new TaikoReplayFrame(tick.StartTime, hitButton ? ReplayButtonState.Right1 : ReplayButtonState.Right2));
hitButton = !hitButton; hitButton = !hitButton;
} }
} }
@ -107,18 +107,18 @@ namespace osu.Game.Rulesets.Taiko.Replays
button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2; button = hitButton ? ReplayButtonState.Left1 : ReplayButtonState.Left2;
} }
Frames.Add(new ReplayFrame(h.StartTime, null, null, button)); Frames.Add(new TaikoReplayFrame(h.StartTime, button));
} }
else else
throw new InvalidOperationException("Unknown hit object type."); throw new InvalidOperationException("Unknown hit object type.");
Frames.Add(new ReplayFrame(endTime + KEY_UP_DELAY, null, null, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(endTime + KEY_UP_DELAY, ReplayButtonState.None));
if (i < Beatmap.HitObjects.Count - 1) if (i < Beatmap.HitObjects.Count - 1)
{ {
double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000; double waitTime = Beatmap.HitObjects[i + 1].StartTime - 1000;
if (waitTime > endTime) if (waitTime > endTime)
Frames.Add(new ReplayFrame(waitTime, null, null, ReplayButtonState.None)); Frames.Add(new TaikoReplayFrame(waitTime, ReplayButtonState.None));
} }
hitButton = !hitButton; hitButton = !hitButton;

View File

@ -0,0 +1,17 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Taiko.Replays
{
public class TaikoReplayFrame : ReplayFrame
{
public override bool IsImportant => MouseLeft || MouseRight;
public TaikoReplayFrame(double time, ReplayButtonState buttons)
: base(time, null, null, buttons)
{
}
}
}

View File

@ -32,6 +32,10 @@
<TreatWarningsAsErrors>false</TreatWarningsAsErrors> <TreatWarningsAsErrors>false</TreatWarningsAsErrors>
</PropertyGroup> </PropertyGroup>
<ItemGroup> <ItemGroup>
<Reference Include="JetBrains.Annotations, Version=11.1.0.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>
@ -91,6 +95,7 @@
<Compile Include="Replays\TaikoAutoGenerator.cs" /> <Compile Include="Replays\TaikoAutoGenerator.cs" />
<Compile Include="Objects\TaikoHitObject.cs" /> <Compile Include="Objects\TaikoHitObject.cs" />
<Compile Include="Objects\TaikoHitObjectDifficulty.cs" /> <Compile Include="Objects\TaikoHitObjectDifficulty.cs" />
<Compile Include="Replays\TaikoReplayFrame.cs" />
<Compile Include="TaikoDifficultyCalculator.cs" /> <Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" /> <Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scoring\TaikoScoreProcessor.cs" /> <Compile Include="Scoring\TaikoScoreProcessor.cs" />

View File

@ -1,5 +1,6 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<packages> <packages>
<package id="JetBrains.Annotations" version="11.1.0" targetFramework="net461" />
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
</packages> </packages>

View File

@ -70,7 +70,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition); Assert.AreEqual(new Vector2(320, 240), sprite.InitialPosition);
Assert.IsTrue(sprite.IsDrawable); Assert.IsTrue(sprite.IsDrawable);
Assert.AreEqual(Anchor.Centre, sprite.Origin); Assert.AreEqual(Anchor.Centre, sprite.Origin);
Assert.AreEqual(Path.Combine("SB", "lyric", "ja-21.png"), sprite.Path); Assert.AreEqual("SB/lyric/ja-21.png", sprite.Path);
var animation = background.Elements.ElementAt(12) as StoryboardAnimation; var animation = background.Elements.ElementAt(12) as StoryboardAnimation;
Assert.NotNull(animation); Assert.NotNull(animation);
@ -82,7 +82,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.IsTrue(animation.IsDrawable); Assert.IsTrue(animation.IsDrawable);
Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType); Assert.AreEqual(AnimationLoopType.LoopForever, animation.LoopType);
Assert.AreEqual(Anchor.Centre, animation.Origin); Assert.AreEqual(Anchor.Centre, animation.Origin);
Assert.AreEqual(Path.Combine("SB", "red jitter", "red_0000.jpg"), animation.Path); Assert.AreEqual("SB/red jitter/red_0000.jpg", animation.Path);
Assert.AreEqual(78993, animation.StartTime); Assert.AreEqual(78993, animation.StartTime);
} }
} }

View File

@ -33,6 +33,10 @@
<Reference Include="DeepEqual, Version=1.6.0.0, Culture=neutral, PublicKeyToken=null"> <Reference Include="DeepEqual, Version=1.6.0.0, Culture=neutral, PublicKeyToken=null">
<HintPath>$(SolutionDir)\packages\DeepEqual.1.6.0.0\lib\net40\DeepEqual.dll</HintPath> <HintPath>$(SolutionDir)\packages\DeepEqual.1.6.0.0\lib\net40\DeepEqual.dll</HintPath>
</Reference> </Reference>
<Reference Include="JetBrains.Annotations, Version=11.1.0.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL"> <Reference Include="nunit.framework, Version=3.8.1.0, Culture=neutral, PublicKeyToken=2638cd05610744eb, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath> <HintPath>$(SolutionDir)\packages\NUnit.3.8.1\lib\net45\nunit.framework.dll</HintPath>
<Private>True</Private> <Private>True</Private>

View File

@ -1,11 +1,12 @@
<?xml version="1.0" encoding="utf-8"?> <?xml version="1.0" encoding="utf-8"?>
<!-- <!--
Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
--> -->
<packages> <packages>
<package id="DeepEqual" version="1.6.0.0" targetFramework="net461" /> <package id="DeepEqual" version="1.6.0.0" targetFramework="net461" />
<package id="NUnit" version="3.8.1" targetFramework="net461" /> <package id="JetBrains.Annotations" version="11.1.0" targetFramework="net461" />
<package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" /> <package id="NUnit" version="3.8.1" targetFramework="net461" />
<package id="System.ValueTuple" version="4.4.0" targetFramework="net461" /> <package id="ppy.OpenTK" version="3.0.11" targetFramework="net461" />
<package id="System.ValueTuple" version="4.4.0" targetFramework="net461" />
</packages> </packages>

View File

@ -301,6 +301,6 @@ namespace osu.Game.Beatmaps.Formats
} }
} }
private string cleanFilename(string path) => FileSafety.PathSanitise(path.Trim('\"')); private string cleanFilename(string path) => FileSafety.PathStandardise(FileSafety.PathSanitise(path.Trim('\"')));
} }
} }

View File

@ -0,0 +1,71 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Configuration;
using osu.Game.Rulesets;
namespace osu.Game.Configuration
{
public abstract class DatabasedConfigManager<T> : ConfigManager<T>
where T : struct
{
private readonly SettingsStore settings;
private readonly int variant;
private readonly List<DatabasedSetting> databasedSettings;
private readonly RulesetInfo ruleset;
protected DatabasedConfigManager(SettingsStore settings, RulesetInfo ruleset = null, int variant = 0)
{
this.settings = settings;
this.ruleset = ruleset;
this.variant = variant;
databasedSettings = settings.Query(ruleset?.ID, variant);
InitialiseDefaults();
}
protected override void PerformLoad()
{
}
protected override bool PerformSave()
{
return true;
}
protected override void AddBindable<TBindable>(T lookup, Bindable<TBindable> bindable)
{
base.AddBindable(lookup, bindable);
var setting = databasedSettings.FirstOrDefault(s => (int)s.Key == (int)(object)lookup);
if (setting != null)
{
bindable.Parse(setting.Value);
}
else
{
settings.Update(setting = new DatabasedSetting
{
Key = lookup,
Value = bindable.Value,
RulesetID = ruleset?.ID,
Variant = variant,
});
databasedSettings.Add(setting);
}
bindable.ValueChanged += v =>
{
setting.Value = v;
settings.Update(setting);
};
}
}
}

View File

@ -0,0 +1,51 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel.DataAnnotations.Schema;
using osu.Game.Database;
namespace osu.Game.Configuration
{
[Table("Settings")]
public class DatabasedSetting : IHasPrimaryKey
{
[DatabaseGenerated(DatabaseGeneratedOption.Identity)]
public int ID { get; set; }
public int? RulesetID { get; set; }
public int? Variant { get; set; }
[Column("Key")]
public int IntKey
{
get => (int)Key;
private set => Key = value;
}
[Column("Value")]
public string StringValue
{
get => Value.ToString();
set => Value = value;
}
public object Key;
public object Value;
public DatabasedSetting(object key, object value)
{
Key = key;
Value = value;
}
/// <summary>
/// Constructor for derived classes that may require serialisation.
/// </summary>
public DatabasedSetting()
{
}
public override string ToString() => $"{Key}=>{Value}";
}
}

View File

@ -8,7 +8,7 @@ using osu.Game.Screens.Select;
namespace osu.Game.Configuration namespace osu.Game.Configuration
{ {
public class OsuConfigManager : ConfigManager<OsuSetting> public class OsuConfigManager : IniConfigManager<OsuSetting>
{ {
protected override void InitialiseDefaults() protected override void InitialiseDefaults()
{ {

View File

@ -0,0 +1,44 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Game.Database;
namespace osu.Game.Configuration
{
public class SettingsStore : DatabaseBackedStore
{
public event Action SettingChanged;
public SettingsStore(Func<OsuDbContext> createContext)
: base(createContext)
{
}
/// <summary>
/// Retrieve <see cref="DatabasedSetting"/>s for a specified ruleset/variant content.
/// </summary>
/// <param name="rulesetId">The ruleset's internal ID.</param>
/// <param name="variant">An optional variant.</param>
/// <returns></returns>
public List<DatabasedSetting> Query(int? rulesetId = null, int? variant = null) =>
GetContext().DatabasedSetting.Where(b => b.RulesetID == rulesetId && b.Variant == variant).ToList();
public void Update(DatabasedSetting setting)
{
var context = GetContext();
var newValue = setting.Value;
Refresh(ref setting);
setting.Value = newValue;
context.SaveChanges();
SettingChanged?.Invoke();
}
}
}

View File

@ -34,8 +34,14 @@ namespace osu.Game.Database
if (context.Entry(obj).State != EntityState.Detached) return; if (context.Entry(obj).State != EntityState.Detached) return;
var id = obj.ID; var id = obj.ID;
obj = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find<T>(id); var foundObject = lookupSource?.SingleOrDefault(t => t.ID == id) ?? context.Find<T>(id);
context.Entry(obj).Reload(); if (foundObject != null)
{
obj = foundObject;
context.Entry(obj).Reload();
}
else
context.Add(obj);
} }
/// <summary> /// <summary>

View File

@ -8,9 +8,10 @@ using Microsoft.EntityFrameworkCore.Diagnostics;
using Microsoft.Extensions.Logging; using Microsoft.Extensions.Logging;
using osu.Framework.Logging; using osu.Framework.Logging;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Input.Bindings; using osu.Game.Configuration;
using osu.Game.IO; using osu.Game.IO;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using DatabasedKeyBinding = osu.Game.Input.Bindings.DatabasedKeyBinding;
using LogLevel = Microsoft.Extensions.Logging.LogLevel; using LogLevel = Microsoft.Extensions.Logging.LogLevel;
namespace osu.Game.Database namespace osu.Game.Database
@ -22,6 +23,7 @@ namespace osu.Game.Database
public DbSet<BeatmapMetadata> BeatmapMetadata { get; set; } public DbSet<BeatmapMetadata> BeatmapMetadata { get; set; }
public DbSet<BeatmapSetInfo> BeatmapSetInfo { get; set; } public DbSet<BeatmapSetInfo> BeatmapSetInfo { get; set; }
public DbSet<DatabasedKeyBinding> DatabasedKeyBinding { get; set; } public DbSet<DatabasedKeyBinding> DatabasedKeyBinding { get; set; }
public DbSet<DatabasedSetting> DatabasedSetting { get; set; }
public DbSet<FileInfo> FileInfo { get; set; } public DbSet<FileInfo> FileInfo { get; set; }
public DbSet<RulesetInfo> RulesetInfo { get; set; } public DbSet<RulesetInfo> RulesetInfo { get; set; }
@ -86,9 +88,11 @@ namespace osu.Game.Database
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.DeletePending); modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.DeletePending);
modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity<BeatmapSetInfo>().HasIndex(b => b.Hash).IsUnique();
modelBuilder.Entity<DatabasedKeyBinding>().HasIndex(b => b.Variant); modelBuilder.Entity<DatabasedKeyBinding>().HasIndex(b => new { b.RulesetID, b.Variant });
modelBuilder.Entity<DatabasedKeyBinding>().HasIndex(b => b.IntAction); modelBuilder.Entity<DatabasedKeyBinding>().HasIndex(b => b.IntAction);
modelBuilder.Entity<DatabasedSetting>().HasIndex(b => new { b.RulesetID, b.Variant });
modelBuilder.Entity<FileInfo>().HasIndex(b => b.Hash).IsUnique(); modelBuilder.Entity<FileInfo>().HasIndex(b => b.Hash).IsUnique();
modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount); modelBuilder.Entity<FileInfo>().HasIndex(b => b.ReferenceCount);

View File

@ -15,7 +15,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
public class IconButton : OsuClickableContainer public class IconButton : OsuClickableContainer
{ {
private const float button_size = 30; public const float BUTTON_SIZE = 30;
private Color4? flashColour; private Color4? flashColour;
/// <summary> /// <summary>
@ -106,7 +106,7 @@ namespace osu.Game.Graphics.UserInterface
{ {
Origin = Anchor.Centre, Origin = Anchor.Centre,
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Size = new Vector2(button_size), Size = new Vector2(BUTTON_SIZE),
CornerRadius = 5, CornerRadius = 5,
Masking = true, Masking = true,
EdgeEffect = new EdgeEffectParameters EdgeEffect = new EdgeEffectParameters

View File

@ -7,6 +7,7 @@ using osu.Framework.Threading;
using OpenTK; using OpenTK;
using osu.Framework.Audio; using osu.Framework.Audio;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume namespace osu.Game.Graphics.UserInterface.Volume
@ -14,6 +15,7 @@ namespace osu.Game.Graphics.UserInterface.Volume
public class VolumeControl : OverlayContainer public class VolumeControl : OverlayContainer
{ {
private readonly VolumeMeter volumeMeterMaster; private readonly VolumeMeter volumeMeterMaster;
private readonly IconButton muteIcon;
protected override bool BlockPassThroughMouse => false; protected override bool BlockPassThroughMouse => false;
@ -34,6 +36,17 @@ namespace osu.Game.Graphics.UserInterface.Volume
Spacing = new Vector2(15, 0), Spacing = new Vector2(15, 0),
Children = new Drawable[] Children = new Drawable[]
{ {
new Container
{
Size = new Vector2(IconButton.BUTTON_SIZE),
Child = muteIcon = new IconButton
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Icon = FontAwesome.fa_volume_up,
Action = () => Adjust(GlobalAction.ToggleMute),
}
},
volumeMeterMaster = new VolumeMeter("Master"), volumeMeterMaster = new VolumeMeter("Master"),
volumeMeterEffect = new VolumeMeter("Effects"), volumeMeterEffect = new VolumeMeter("Effects"),
volumeMeterMusic = new VolumeMeter("Music") volumeMeterMusic = new VolumeMeter("Music")
@ -46,18 +59,10 @@ namespace osu.Game.Graphics.UserInterface.Volume
{ {
base.LoadComplete(); base.LoadComplete();
volumeMeterMaster.Bindable.ValueChanged += volumeChanged; volumeMeterMaster.Bindable.ValueChanged += _ => settingChanged();
volumeMeterEffect.Bindable.ValueChanged += volumeChanged; volumeMeterEffect.Bindable.ValueChanged += _ => settingChanged();
volumeMeterMusic.Bindable.ValueChanged += volumeChanged; volumeMeterMusic.Bindable.ValueChanged += _ => settingChanged();
} muted.ValueChanged += _ => settingChanged();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
volumeMeterMaster.Bindable.ValueChanged -= volumeChanged;
volumeMeterEffect.Bindable.ValueChanged -= volumeChanged;
volumeMeterMusic.Bindable.ValueChanged -= volumeChanged;
} }
public bool Adjust(GlobalAction action) public bool Adjust(GlobalAction action)
@ -76,23 +81,45 @@ namespace osu.Game.Graphics.UserInterface.Volume
else else
volumeMeterMaster.Increase(); volumeMeterMaster.Increase();
return true; return true;
case GlobalAction.ToggleMute:
Show();
muted.Toggle();
return true;
} }
return false; return false;
} }
private void volumeChanged(double newVolume) private void settingChanged()
{ {
Show(); Show();
schedulePopOut(); schedulePopOut();
} }
private readonly BindableDouble muteAdjustment = new BindableDouble();
private readonly BindableBool muted = new BindableBool();
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(AudioManager audio) private void load(AudioManager audio)
{ {
volumeMeterMaster.Bindable.BindTo(audio.Volume); volumeMeterMaster.Bindable.BindTo(audio.Volume);
volumeMeterEffect.Bindable.BindTo(audio.VolumeSample); volumeMeterEffect.Bindable.BindTo(audio.VolumeSample);
volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack); volumeMeterMusic.Bindable.BindTo(audio.VolumeTrack);
muted.ValueChanged += mute =>
{
if (mute)
{
audio.AddAdjustment(AdjustableProperty.Volume, muteAdjustment);
muteIcon.Icon = FontAwesome.fa_volume_off;
}
else
{
audio.RemoveAdjustment(AdjustableProperty.Volume, muteAdjustment);
muteIcon.Icon = FontAwesome.fa_volume_up;
}
};
} }
private ScheduledDelegate popOutDelegate; private ScheduledDelegate popOutDelegate;

View File

@ -70,11 +70,8 @@ namespace osu.Game.Graphics.UserInterface.Volume
public double Volume public double Volume
{ {
get { return Bindable.Value; } get => Bindable.Value;
private set private set => Bindable.Value = value;
{
Bindable.Value = value;
}
} }
public void Increase() public void Increase()

View File

@ -14,7 +14,7 @@ namespace osu.Game.Input.Bindings
/// A KeyBindingInputManager with a database backing for custom overrides. /// A KeyBindingInputManager with a database backing for custom overrides.
/// </summary> /// </summary>
/// <typeparam name="T">The type of the custom action.</typeparam> /// <typeparam name="T">The type of the custom action.</typeparam>
public class DatabasedKeyBindingInputManager<T> : KeyBindingContainer<T> public class DatabasedKeyBindingContainer<T> : KeyBindingContainer<T>
where T : struct where T : struct
{ {
private readonly RulesetInfo ruleset; private readonly RulesetInfo ruleset;
@ -31,7 +31,7 @@ namespace osu.Game.Input.Bindings
/// <param name="ruleset">A reference to identify the current <see cref="Ruleset"/>. Used to lookup mappings. Null for global mappings.</param> /// <param name="ruleset">A reference to identify the current <see cref="Ruleset"/>. Used to lookup mappings. Null for global mappings.</param>
/// <param name="variant">An optional variant for the specified <see cref="Ruleset"/>. Used when a ruleset has more than one possible keyboard layouts.</param> /// <param name="variant">An optional variant for the specified <see cref="Ruleset"/>. Used when a ruleset has more than one possible keyboard layouts.</param>
/// <param name="simultaneousMode">Specify how to deal with multiple matches of <see cref="KeyCombination"/>s and <see cref="T"/>s.</param> /// <param name="simultaneousMode">Specify how to deal with multiple matches of <see cref="KeyCombination"/>s and <see cref="T"/>s.</param>
public DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None) public DatabasedKeyBindingContainer(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None)
: base(simultaneousMode) : base(simultaneousMode)
{ {
this.ruleset = ruleset; this.ruleset = ruleset;

View File

@ -10,11 +10,11 @@ using osu.Framework.Input.Bindings;
namespace osu.Game.Input.Bindings namespace osu.Game.Input.Bindings
{ {
public class GlobalKeyBindingInputManager : DatabasedKeyBindingInputManager<GlobalAction>, IHandleGlobalInput public class GlobalActionContainer : DatabasedKeyBindingContainer<GlobalAction>, IHandleGlobalInput
{ {
private readonly Drawable handler; private readonly Drawable handler;
public GlobalKeyBindingInputManager(OsuGameBase game) public GlobalActionContainer(OsuGameBase game)
{ {
if (game is IKeyBindingHandler<GlobalAction>) if (game is IKeyBindingHandler<GlobalAction>)
handler = game; handler = game;
@ -29,10 +29,11 @@ namespace osu.Game.Input.Bindings
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings), new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar), new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings), new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
new KeyBinding(new[] { InputKey.Up }, GlobalAction.IncreaseVolume), new KeyBinding(InputKey.Up, GlobalAction.IncreaseVolume),
new KeyBinding(new[] { InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume), new KeyBinding(InputKey.MouseWheelUp, GlobalAction.IncreaseVolume),
new KeyBinding(new[] { InputKey.Down }, GlobalAction.DecreaseVolume), new KeyBinding(InputKey.Down, GlobalAction.DecreaseVolume),
new KeyBinding(new[] { InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume), new KeyBinding(InputKey.MouseWheelDown, GlobalAction.DecreaseVolume),
new KeyBinding(InputKey.F4, GlobalAction.ToggleMute),
}; };
public IEnumerable<KeyBinding> InGameKeyBindings => new[] public IEnumerable<KeyBinding> InGameKeyBindings => new[]
@ -63,6 +64,8 @@ namespace osu.Game.Input.Bindings
IncreaseVolume, IncreaseVolume,
[Description("Decrease Volume")] [Description("Decrease Volume")]
DecreaseVolume, DecreaseVolume,
[Description("Toggle mute")]
ToggleMute,
// In-Game Keybindings // In-Game Keybindings
[Description("Skip Cutscene")] [Description("Skip Cutscene")]

View File

@ -0,0 +1,329 @@
// <auto-generated />
using Microsoft.EntityFrameworkCore;
using Microsoft.EntityFrameworkCore.Infrastructure;
using Microsoft.EntityFrameworkCore.Metadata;
using Microsoft.EntityFrameworkCore.Migrations;
using Microsoft.EntityFrameworkCore.Storage;
using osu.Game.Database;
using System;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20180125143340_Settings")]
partial class Settings
{
protected override void BuildTargetModel(ModelBuilder modelBuilder)
{
#pragma warning disable 612, 618
modelBuilder
.HasAnnotation("ProductVersion", "2.0.0-rtm-26452");
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapDifficulty", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<float>("ApproachRate");
b.Property<float>("CircleSize");
b.Property<float>("DrainRate");
b.Property<float>("OverallDifficulty");
b.Property<float>("SliderMultiplier");
b.Property<float>("SliderTickRate");
b.HasKey("ID");
b.ToTable("BeatmapDifficulty");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("AudioLeadIn");
b.Property<int>("BaseDifficultyID");
b.Property<int>("BeatDivisor");
b.Property<int>("BeatmapSetInfoID");
b.Property<bool>("Countdown");
b.Property<double>("DistanceSpacing");
b.Property<int>("GridSize");
b.Property<string>("Hash");
b.Property<bool>("Hidden");
b.Property<bool>("LetterboxInBreaks");
b.Property<string>("MD5Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapID");
b.Property<string>("Path");
b.Property<int>("RulesetID");
b.Property<bool>("SpecialStyle");
b.Property<float>("StackLeniency");
b.Property<double>("StarDifficulty");
b.Property<string>("StoredBookmarks");
b.Property<double>("TimelineZoom");
b.Property<string>("Version");
b.Property<bool>("WidescreenStoryboard");
b.HasKey("ID");
b.HasIndex("BaseDifficultyID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("MD5Hash")
.IsUnique();
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapID")
.IsUnique();
b.HasIndex("RulesetID");
b.ToTable("BeatmapInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapMetadata", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Artist");
b.Property<string>("ArtistUnicode");
b.Property<string>("AudioFile");
b.Property<string>("AuthorString")
.HasColumnName("Author");
b.Property<string>("BackgroundFile");
b.Property<int>("PreviewTime");
b.Property<string>("Source");
b.Property<string>("Tags");
b.Property<string>("Title");
b.Property<string>("TitleUnicode");
b.HasKey("ID");
b.ToTable("BeatmapMetadata");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("BeatmapSetInfoID");
b.Property<int>("FileInfoID");
b.Property<string>("Filename")
.IsRequired();
b.HasKey("ID");
b.HasIndex("BeatmapSetInfoID");
b.HasIndex("FileInfoID");
b.ToTable("BeatmapSetFileInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("DeletePending");
b.Property<string>("Hash");
b.Property<int?>("MetadataID");
b.Property<int?>("OnlineBeatmapSetID");
b.Property<bool>("Protected");
b.HasKey("ID");
b.HasIndex("DeletePending");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("MetadataID");
b.HasIndex("OnlineBeatmapSetID")
.IsUnique();
b.ToTable("BeatmapSetInfo");
});
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntAction")
.HasColumnName("Action");
b.Property<string>("KeysString")
.HasColumnName("Keys");
b.Property<int?>("RulesetID");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("IntAction");
b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding");
});
modelBuilder.Entity("osu.Game.IO.FileInfo", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<string>("Hash");
b.Property<int>("ReferenceCount");
b.HasKey("ID");
b.HasIndex("Hash")
.IsUnique();
b.HasIndex("ReferenceCount");
b.ToTable("FileInfo");
});
modelBuilder.Entity("osu.Game.Rulesets.RulesetInfo", b =>
{
b.Property<int?>("ID")
.ValueGeneratedOnAdd();
b.Property<bool>("Available");
b.Property<string>("InstantiationInfo");
b.Property<string>("Name");
b.Property<string>("ShortName");
b.HasKey("ID");
b.HasIndex("Available");
b.HasIndex("ShortName")
.IsUnique();
b.ToTable("RulesetInfo");
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapDifficulty", "BaseDifficulty")
.WithMany()
.HasForeignKey("BaseDifficultyID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo", "BeatmapSet")
.WithMany("Beatmaps")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("Beatmaps")
.HasForeignKey("MetadataID");
b.HasOne("osu.Game.Rulesets.RulesetInfo", "Ruleset")
.WithMany()
.HasForeignKey("RulesetID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetFileInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapSetInfo")
.WithMany("Files")
.HasForeignKey("BeatmapSetInfoID")
.OnDelete(DeleteBehavior.Cascade);
b.HasOne("osu.Game.IO.FileInfo", "FileInfo")
.WithMany()
.HasForeignKey("FileInfoID")
.OnDelete(DeleteBehavior.Cascade);
});
modelBuilder.Entity("osu.Game.Beatmaps.BeatmapSetInfo", b =>
{
b.HasOne("osu.Game.Beatmaps.BeatmapMetadata", "Metadata")
.WithMany("BeatmapSets")
.HasForeignKey("MetadataID");
});
#pragma warning restore 612, 618
}
}
}

View File

@ -0,0 +1,57 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
namespace osu.Game.Migrations
{
public partial class Settings : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropIndex(
name: "IX_KeyBinding_Variant",
table: "KeyBinding");
migrationBuilder.CreateTable(
name: "Settings",
columns: table => new
{
ID = table.Column<int>(type: "INTEGER", nullable: false)
.Annotation("Sqlite:Autoincrement", true),
Key = table.Column<int>(type: "INTEGER", nullable: false),
RulesetID = table.Column<int>(type: "INTEGER", nullable: true),
Value = table.Column<string>(type: "TEXT", nullable: true),
Variant = table.Column<int>(type: "INTEGER", nullable: true)
},
constraints: table =>
{
table.PrimaryKey("PK_Settings", x => x.ID);
});
migrationBuilder.CreateIndex(
name: "IX_KeyBinding_RulesetID_Variant",
table: "KeyBinding",
columns: new[] { "RulesetID", "Variant" });
migrationBuilder.CreateIndex(
name: "IX_Settings_RulesetID_Variant",
table: "Settings",
columns: new[] { "RulesetID", "Variant" });
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.DropTable(
name: "Settings");
migrationBuilder.DropIndex(
name: "IX_KeyBinding_RulesetID_Variant",
table: "KeyBinding");
migrationBuilder.CreateIndex(
name: "IX_KeyBinding_Variant",
table: "KeyBinding",
column: "Variant");
}
}
}

View File

@ -0,0 +1,25 @@
using Microsoft.EntityFrameworkCore.Migrations;
using System;
using System.Collections.Generic;
using Microsoft.EntityFrameworkCore.Infrastructure;
using osu.Game.Database;
using osu.Game.Input.Bindings;
namespace osu.Game.Migrations
{
[DbContext(typeof(OsuDbContext))]
[Migration("20180131154205_AddMuteBinding")]
public partial class AddMuteBinding : Migration
{
protected override void Up(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action + 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action >= {(int)GlobalAction.ToggleMute}");
}
protected override void Down(MigrationBuilder migrationBuilder)
{
migrationBuilder.Sql($"DELETE FROM KeyBinding WHERE RulesetID IS NULL AND Variant IS NULL AND Action = {(int)GlobalAction.ToggleMute}");
migrationBuilder.Sql($"UPDATE KeyBinding SET Action = Action - 1 WHERE RulesetID IS NULL AND Variant IS NULL AND Action > {(int)GlobalAction.ToggleMute}");
}
}
}

View File

@ -193,6 +193,28 @@ namespace osu.Game.Migrations
b.ToTable("BeatmapSetInfo"); b.ToTable("BeatmapSetInfo");
}); });
modelBuilder.Entity("osu.Game.Configuration.DatabasedSetting", b =>
{
b.Property<int>("ID")
.ValueGeneratedOnAdd();
b.Property<int>("IntKey")
.HasColumnName("Key");
b.Property<int?>("RulesetID");
b.Property<string>("StringValue")
.HasColumnName("Value");
b.Property<int?>("Variant");
b.HasKey("ID");
b.HasIndex("RulesetID", "Variant");
b.ToTable("Settings");
});
modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b => modelBuilder.Entity("osu.Game.Input.Bindings.DatabasedKeyBinding", b =>
{ {
b.Property<int>("ID") b.Property<int>("ID")
@ -212,7 +234,7 @@ namespace osu.Game.Migrations
b.HasIndex("IntAction"); b.HasIndex("IntAction");
b.HasIndex("Variant"); b.HasIndex("RulesetID", "Variant");
b.ToTable("KeyBinding"); b.ToTable("KeyBinding");
}); });

View File

@ -126,6 +126,7 @@ namespace osu.Game.Online.API
userReq.Success += u => userReq.Success += u =>
{ {
LocalUser.Value = u; LocalUser.Value = u;
Username = LocalUser.Value.Username;
failureCount = 0; failureCount = 0;
//we're connected! //we're connected!

View File

@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System; using System;
using System.Collections.Generic;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Screens; using osu.Framework.Screens;
using osu.Game.Configuration; using osu.Game.Configuration;
@ -27,6 +28,7 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets; using osu.Game.Rulesets;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Input.Bindings; using osu.Game.Input.Bindings;
using osu.Game.Rulesets.Mods;
using OpenTK.Graphics; using OpenTK.Graphics;
namespace osu.Game namespace osu.Game
@ -71,6 +73,7 @@ namespace osu.Game
private OsuScreen screenStack; private OsuScreen screenStack;
private VolumeControl volume; private VolumeControl volume;
private OnScreenDisplay onscreenDisplay;
private Bindable<int> configRuleset; private Bindable<int> configRuleset;
public Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>(); public Bindable<RulesetInfo> Ruleset = new Bindable<RulesetInfo>();
@ -79,6 +82,9 @@ namespace osu.Game
private SettingsOverlay settings; private SettingsOverlay settings;
// todo: move this to SongSelect once Screen has the ability to unsuspend.
public readonly Bindable<IEnumerable<Mod>> SelectedMods = new Bindable<IEnumerable<Mod>>(new List<Mod>());
public OsuGame(string[] args = null) public OsuGame(string[] args = null)
{ {
this.args = args; this.args = args;
@ -110,7 +116,7 @@ namespace osu.Game
Task.Run(() => BeatmapManager.Import(paths.ToArray())); Task.Run(() => BeatmapManager.Import(paths.ToArray()));
} }
dependencies.CacheAs<OsuGame>(this); dependencies.CacheAs(this);
configRuleset = LocalConfig.GetBindable<int>(OsuSetting.Ruleset); configRuleset = LocalConfig.GetBindable<int>(OsuSetting.Ruleset);
Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First(); Ruleset.Value = RulesetStore.GetRuleset(configRuleset.Value) ?? RulesetStore.AvailableRulesets.First();
@ -195,7 +201,7 @@ namespace osu.Game
}, overlayContent.Add); }, overlayContent.Add);
loadComponentSingleFile(volume = new VolumeControl(), Add); loadComponentSingleFile(volume = new VolumeControl(), Add);
loadComponentSingleFile(new OnScreenDisplay(), Add); loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add);
//overlay elements //overlay elements
loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add); loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
@ -232,6 +238,7 @@ namespace osu.Game
forwardLoggedErrorsToNotifications(); forwardLoggedErrorsToNotifications();
dependencies.Cache(settings); dependencies.Cache(settings);
dependencies.Cache(onscreenDisplay);
dependencies.Cache(social); dependencies.Cache(social);
dependencies.Cache(direct); dependencies.Cache(direct);
dependencies.Cache(chat); dependencies.Cache(chat);

View File

@ -44,6 +44,8 @@ namespace osu.Game
protected KeyBindingStore KeyBindingStore; protected KeyBindingStore KeyBindingStore;
protected SettingsStore SettingsStore;
protected CursorOverrideContainer CursorOverrideContainer; protected CursorOverrideContainer CursorOverrideContainer;
protected override string MainResourceFile => @"osu.Game.Resources.dll"; protected override string MainResourceFile => @"osu.Game.Resources.dll";
@ -93,7 +95,7 @@ namespace osu.Game
dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures")))); dependencies.Cache(new LargeTextureStore(new RawTextureLoaderStore(new NamespacedResourceStore<byte[]>(Resources, @"Textures"))));
dependencies.CacheAs<OsuGameBase>(this); dependencies.CacheAs(this);
dependencies.Cache(LocalConfig); dependencies.Cache(LocalConfig);
runMigrations(); runMigrations();
@ -109,6 +111,7 @@ namespace osu.Game
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory.GetContext, RulesetStore, API, Host)); dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory.GetContext, RulesetStore, API, Host));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory.GetContext, Host, BeatmapManager, RulesetStore)); dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory.GetContext, Host, BeatmapManager, RulesetStore));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory.GetContext, RulesetStore)); dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory.GetContext, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory.GetContext));
dependencies.Cache(new OsuColour()); dependencies.Cache(new OsuColour());
//this completely overrides the framework default. will need to change once we make a proper FontStore. //this completely overrides the framework default. will need to change once we make a proper FontStore.
@ -209,10 +212,10 @@ namespace osu.Game
{ {
base.LoadComplete(); base.LoadComplete();
GlobalKeyBindingInputManager globalBinding; GlobalActionContainer globalBinding;
CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both }; CursorOverrideContainer = new CursorOverrideContainer { RelativeSizeAxes = Axes.Both };
CursorOverrideContainer.Child = globalBinding = new GlobalKeyBindingInputManager(this) CursorOverrideContainer.Child = globalBinding = new GlobalActionContainer(this)
{ {
RelativeSizeAxes = Axes.Both, RelativeSizeAxes = Axes.Both,
Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both } Child = content = new OsuTooltipContainer(CursorOverrideContainer.Cursor) { RelativeSizeAxes = Axes.Both }

View File

@ -12,7 +12,7 @@ namespace osu.Game.Overlays.KeyBinding
public override FontAwesome Icon => FontAwesome.fa_osu_hot; public override FontAwesome Icon => FontAwesome.fa_osu_hot;
public override string Header => "Global"; public override string Header => "Global";
public GlobalKeyBindingsSection(GlobalKeyBindingInputManager manager) public GlobalKeyBindingsSection(GlobalActionContainer manager)
{ {
Add(new DefaultBindingsSubsection(manager)); Add(new DefaultBindingsSubsection(manager));
Add(new InGameKeyBindingsSubsection(manager)); Add(new InGameKeyBindingsSubsection(manager));
@ -23,7 +23,7 @@ namespace osu.Game.Overlays.KeyBinding
{ {
protected override string Header => string.Empty; protected override string Header => string.Empty;
public DefaultBindingsSubsection(GlobalKeyBindingInputManager manager) public DefaultBindingsSubsection(GlobalActionContainer manager)
: base(null) : base(null)
{ {
Defaults = manager.GlobalKeyBindings; Defaults = manager.GlobalKeyBindings;
@ -34,7 +34,7 @@ namespace osu.Game.Overlays.KeyBinding
{ {
protected override string Header => "In Game"; protected override string Header => "In Game";
public InGameKeyBindingsSubsection(GlobalKeyBindingInputManager manager) : base(null) public InGameKeyBindingsSubsection(GlobalActionContainer manager) : base(null)
{ {
Defaults = manager.InGameKeyBindings; Defaults = manager.InGameKeyBindings;
} }

View File

@ -15,7 +15,7 @@ namespace osu.Game.Overlays
protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!"); protected override Drawable CreateHeader() => new SettingsHeader("key configuration", "Customise your keys!");
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(RulesetStore rulesets, GlobalKeyBindingInputManager global) private void load(RulesetStore rulesets, GlobalActionContainer global)
{ {
AddSection(new GlobalKeyBindingsSection(global)); AddSection(new GlobalKeyBindingsSection(global));

View File

@ -188,17 +188,19 @@ namespace osu.Game.Overlays.Mods
start = Mods.Length - 1; start = Mods.Length - 1;
for (int i = start; i < Mods.Length && i >= 0; i += direction) for (int i = start; i < Mods.Length && i >= 0; i += direction)
{ if (SelectAt(i)) return;
if (Mods[i].HasImplementation)
{
changeSelectedIndex(i);
return;
}
}
Deselect(); Deselect();
} }
public bool SelectAt(int index)
{
if (!Mods[index].HasImplementation) return false;
changeSelectedIndex(index);
return true;
}
public void Deselect() => changeSelectedIndex(-1); public void Deselect() => changeSelectedIndex(-1);
private void displayMod(Mod mod) private void displayMod(Mod mod)

View File

@ -113,6 +113,23 @@ namespace osu.Game.Overlays.Mods
} }
} }
/// <summary>
/// Select one or more mods in this section.
/// </summary>
/// <param name="mods">The types of <see cref="Mod"/>s which should be deselected.</param>
public void SelectTypes(IEnumerable<Mod> mods)
{
foreach (var button in buttons)
{
for (int i = 0; i < button.Mods.Length; i++)
{
foreach (var mod in mods)
if (mod.GetType().IsInstanceOfType(button.Mods[i]))
button.SelectAt(i);
}
}
}
protected ModSection() protected ModSection()
{ {
AutoSizeAxes = Axes.Y; AutoSizeAxes = Axes.Y;

View File

@ -51,6 +51,8 @@ namespace osu.Game.Overlays.Mods
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets) private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets)
{ {
SelectedMods.ValueChanged += selectedModsChanged;
LowMultiplierColour = colours.Red; LowMultiplierColour = colours.Red;
HighMultiplierColour = colours.Green; HighMultiplierColour = colours.Green;
@ -63,6 +65,37 @@ namespace osu.Game.Overlays.Mods
Ruleset.TriggerChange(); Ruleset.TriggerChange();
} }
private void selectedModsChanged(IEnumerable<Mod> obj)
{
foreach (ModSection section in ModSectionsContainer.Children)
section.SelectTypes(obj);
updateMods();
}
private void updateMods()
{
double multiplier = 1.0;
bool ranked = true;
foreach (Mod mod in SelectedMods.Value)
{
multiplier *= mod.ScoreMultiplier;
ranked &= mod.Ranked;
}
MultiplierLabel.Text = $"{multiplier:N2}x";
if (!ranked)
MultiplierLabel.Text += " (Unranked)";
if (multiplier > 1.0)
MultiplierLabel.FadeColour(HighMultiplierColour, 200);
else if (multiplier < 1.0)
MultiplierLabel.FadeColour(LowMultiplierColour, 200);
else
MultiplierLabel.FadeColour(Color4.White, 200);
}
protected override void PopOut() protected override void PopOut()
{ {
base.PopOut(); base.PopOut();
@ -97,6 +130,7 @@ namespace osu.Game.Overlays.Mods
{ {
foreach (ModSection section in ModSectionsContainer.Children) foreach (ModSection section in ModSectionsContainer.Children)
section.DeselectAll(); section.DeselectAll();
refreshSelectedMods(); refreshSelectedMods();
} }
@ -119,30 +153,7 @@ namespace osu.Game.Overlays.Mods
refreshSelectedMods(); refreshSelectedMods();
} }
private void refreshSelectedMods() private void refreshSelectedMods() => SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray();
{
SelectedMods.Value = ModSectionsContainer.Children.SelectMany(s => s.SelectedMods).ToArray();
double multiplier = 1.0;
bool ranked = true;
foreach (Mod mod in SelectedMods.Value)
{
multiplier *= mod.ScoreMultiplier;
ranked &= mod.Ranked;
}
MultiplierLabel.Text = $"{multiplier:N2}x";
if (!ranked)
MultiplierLabel.Text += " (Unranked)";
if (multiplier > 1.0)
MultiplierLabel.FadeColour(HighMultiplierColour, 200);
else if (multiplier < 1.0)
MultiplierLabel.FadeColour(LowMultiplierColour, 200);
else
MultiplierLabel.FadeColour(Color4.White, 200);
}
public ModSelectOverlay() public ModSelectOverlay()
{ {

View File

@ -5,8 +5,6 @@ using System;
using System.Diagnostics; using System.Diagnostics;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions; using osu.Framework.Extensions.Color4Extensions;
@ -20,10 +18,12 @@ using osu.Framework.Localisation;
using osu.Framework.Threading; using osu.Framework.Threading;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Overlays.Music;
using osu.Game.Graphics.UserInterface;
using osu.Game.Graphics.Containers; using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
using osu.Game.Overlays.Music;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays namespace osu.Game.Overlays
{ {
@ -65,6 +65,12 @@ namespace osu.Game.Overlays
AlwaysPresent = true; AlwaysPresent = true;
} }
protected override bool OnDragStart(InputState state)
{
base.OnDragStart(state);
return true;
}
protected override bool OnDrag(InputState state) protected override bool OnDrag(InputState state)
{ {
if (base.OnDrag(state)) return true; if (base.OnDrag(state)) return true;

View File

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic; using System.Collections.Generic;
using osu.Framework.Allocation; using osu.Framework.Allocation;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Extensions; using osu.Framework.Configuration.Tracking;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes; using osu.Framework.Graphics.Shapes;
@ -118,43 +118,62 @@ namespace osu.Game.Overlays
[BackgroundDependencyLoader] [BackgroundDependencyLoader]
private void load(FrameworkConfigManager frameworkConfig) private void load(FrameworkConfigManager frameworkConfig)
{ {
trackSetting(frameworkConfig.GetBindable<FrameSync>(FrameworkSetting.FrameSync), v => display(v, "Frame Limiter", v.GetDescription(), "Ctrl+F7")); BeginTracking(this, frameworkConfig);
trackSetting(frameworkConfig.GetBindable<string>(FrameworkSetting.AudioDevice), v => display(v, "Audio Device", string.IsNullOrEmpty(v) ? "Default" : v, v));
trackSetting(frameworkConfig.GetBindable<bool>(FrameworkSetting.ShowLogOverlay), v => display(v, "Debug Logs", v ? "visible" : "hidden", "Ctrl+F10"));
void displayResolution() => display(null, "Screen Resolution", frameworkConfig.Get<int>(FrameworkSetting.Width) + "x" + frameworkConfig.Get<int>(FrameworkSetting.Height));
trackSetting(frameworkConfig.GetBindable<int>(FrameworkSetting.Width), v => displayResolution());
trackSetting(frameworkConfig.GetBindable<int>(FrameworkSetting.Height), v => displayResolution());
trackSetting(frameworkConfig.GetBindable<double>(FrameworkSetting.CursorSensitivity), v => display(v, "Cursor Sensitivity", v.ToString(@"0.##x"), "Ctrl+Alt+R to reset"));
trackSetting(frameworkConfig.GetBindable<string>(FrameworkSetting.ActiveInputHandlers),
delegate (string v)
{
bool raw = v.Contains("Raw");
display(raw, "Raw Input", raw ? "enabled" : "disabled", "Ctrl+Alt+R to reset");
});
trackSetting(frameworkConfig.GetBindable<WindowMode>(FrameworkSetting.WindowMode), v => display(v, "Screen Mode", v.ToString(), "Alt+Enter"));
} }
private readonly List<IBindable> references = new List<IBindable>(); private readonly Dictionary<(object, IConfigManager), TrackedSettings> trackedConfigManagers = new Dictionary<(object, IConfigManager), TrackedSettings>();
private void trackSetting<T>(Bindable<T> bindable, Action<T> action) /// <summary>
/// Registers a <see cref="ConfigManager{T}"/> to have its settings tracked by this <see cref="OnScreenDisplay"/>.
/// </summary>
/// <param name="source">The object that is registering the <see cref="ConfigManager{T}"/> to be tracked.</param>
/// <param name="configManager">The <see cref="ConfigManager{T}"/> to be tracked.</param>
/// <exception cref="ArgumentNullException">If <paramref name="configManager"/> is null.</exception>
/// <exception cref="InvalidOperationException">If <paramref name="configManager"/> is already being tracked from the same <paramref name="source"/>.</exception>
public void BeginTracking(object source, ITrackableConfigManager configManager)
{ {
// we need to keep references as we bind if (configManager == null) throw new ArgumentNullException(nameof(configManager));
references.Add(bindable);
bindable.ValueChanged += action; if (trackedConfigManagers.ContainsKey((source, configManager)))
throw new InvalidOperationException($"{nameof(configManager)} is already registered.");
var trackedSettings = configManager.CreateTrackedSettings();
if (trackedSettings == null)
return;
configManager.LoadInto(trackedSettings);
trackedSettings.SettingChanged += display;
trackedConfigManagers.Add((source, configManager), trackedSettings);
} }
private void display(object rawValue, string settingName, string settingValue, string shortcut = @"") /// <summary>
/// Unregisters a <see cref="ConfigManager{T}"/> from having its settings tracked by this <see cref="OnScreenDisplay"/>.
/// </summary>
/// <param name="source">The object that registered the <see cref="ConfigManager{T}"/> to be tracked.</param>
/// <param name="configManager">The <see cref="ConfigManager{T}"/> that is being tracked.</param>
/// <exception cref="ArgumentNullException">If <paramref name="configManager"/> is null.</exception>
/// <exception cref="InvalidOperationException">If <paramref name="configManager"/> is not being tracked from the same <see cref="source"/>.</exception>
public void StopTracking(object source, ITrackableConfigManager configManager)
{
if (configManager == null) throw new ArgumentNullException(nameof(configManager));
if (!trackedConfigManagers.TryGetValue((source, configManager), out var existing))
throw new InvalidOperationException($"{nameof(configManager)} is not registered.");
existing.Unload();
existing.SettingChanged -= display;
trackedConfigManagers.Remove((source, configManager));
}
private void display(SettingDescription description)
{ {
Schedule(() => Schedule(() =>
{ {
textLine1.Text = settingName.ToUpper(); textLine1.Text = description.Name.ToUpper();
textLine2.Text = settingValue; textLine2.Text = description.Value;
textLine3.Text = shortcut.ToUpper(); textLine3.Text = description.Shortcut.ToUpper();
box.Animate( box.Animate(
b => b.FadeIn(500, Easing.OutQuint), b => b.FadeIn(500, Easing.OutQuint),
@ -167,16 +186,16 @@ namespace osu.Game.Overlays
int optionCount = 0; int optionCount = 0;
int selectedOption = -1; int selectedOption = -1;
if (rawValue is bool) if (description.RawValue is bool)
{ {
optionCount = 1; optionCount = 1;
if ((bool)rawValue) selectedOption = 0; if ((bool)description.RawValue) selectedOption = 0;
} }
else if (rawValue is Enum) else if (description.RawValue is Enum)
{ {
var values = Enum.GetValues(rawValue.GetType()); var values = Enum.GetValues(description.RawValue.GetType());
optionCount = values.Length; optionCount = values.Length;
selectedOption = Convert.ToInt32(rawValue); selectedOption = Convert.ToInt32(description.RawValue);
} }
textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre; textLine2.Origin = optionCount > 0 ? Anchor.BottomCentre : Anchor.Centre;

View File

@ -16,7 +16,7 @@ namespace osu.Game.Overlays.Toolbar
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(SettingsOverlay settings) private void load(MainSettings settings)
{ {
StateContainer = settings; StateContainer = settings;
} }

View File

@ -0,0 +1,11 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration.Tracking;
namespace osu.Game.Rulesets.Configuration
{
public interface IRulesetConfigManager : ITrackableConfigManager
{
}
}

View File

@ -0,0 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Configuration;
namespace osu.Game.Rulesets.Configuration
{
public abstract class RulesetConfigManager<T> : DatabasedConfigManager<T>, IRulesetConfigManager
where T : struct
{
protected RulesetConfigManager(SettingsStore settings, RulesetInfo ruleset, int variant) : base(settings, ruleset, variant)
{
}
}
}

View File

@ -136,7 +136,10 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// </summary> /// </summary>
public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState; public event Action<DrawableHitObject, ArmedState> ApplyCustomUpdateState;
protected void PlaySamples() => Samples.ForEach(s => s?.Play()); /// <summary>
/// Plays all the hitsounds for this <see cref="DrawableHitObject"/>.
/// </summary>
public void PlaySamples() => Samples.ForEach(s => s?.Play());
protected override void Update() protected override void Update()
{ {

View File

@ -16,6 +16,9 @@ using System.Linq;
using osu.Framework.Configuration; using osu.Framework.Configuration;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Overlays;
using osu.Game.Rulesets.Configuration;
using osu.Game.Rulesets.Replays; using osu.Game.Rulesets.Replays;
using osu.Game.Rulesets.Scoring; using osu.Game.Rulesets.Scoring;
using OpenTK; using OpenTK;
@ -35,6 +38,11 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
public bool AspectAdjust = true; public bool AspectAdjust = true;
/// <summary>
/// The selected variant.
/// </summary>
public virtual int Variant => 0;
/// <summary> /// <summary>
/// The input manager for this RulesetContainer. /// The input manager for this RulesetContainer.
/// </summary> /// </summary>
@ -65,6 +73,14 @@ namespace osu.Game.Rulesets.UI
protected readonly Ruleset Ruleset; protected readonly Ruleset Ruleset;
private IRulesetConfigManager rulesetConfig;
private OnScreenDisplay onScreenDisplay;
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
/// <summary> /// <summary>
/// A visual representation of a <see cref="Rulesets.Ruleset"/>. /// A visual representation of a <see cref="Rulesets.Ruleset"/>.
/// </summary> /// </summary>
@ -77,6 +93,20 @@ namespace osu.Game.Rulesets.UI
Cursor = CreateCursor(); Cursor = CreateCursor();
} }
[BackgroundDependencyLoader(true)]
private void load(OnScreenDisplay onScreenDisplay, SettingsStore settings)
{
this.onScreenDisplay = onScreenDisplay;
rulesetConfig = CreateConfig(Ruleset, settings);
if (rulesetConfig != null)
{
dependencies.Cache(rulesetConfig);
onScreenDisplay?.BeginTracking(this, rulesetConfig);
}
}
public abstract ScoreProcessor CreateScoreProcessor(); public abstract ScoreProcessor CreateScoreProcessor();
/// <summary> /// <summary>
@ -110,11 +140,24 @@ namespace osu.Game.Rulesets.UI
/// </summary> /// </summary>
protected virtual CursorContainer CreateCursor() => null; protected virtual CursorContainer CreateCursor() => null;
protected virtual IRulesetConfigManager CreateConfig(Ruleset ruleset, SettingsStore settings) => null;
/// <summary> /// <summary>
/// Creates a Playfield. /// Creates a Playfield.
/// </summary> /// </summary>
/// <returns>The Playfield.</returns> /// <returns>The Playfield.</returns>
protected abstract Playfield CreatePlayfield(); protected abstract Playfield CreatePlayfield();
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (rulesetConfig != null)
{
onScreenDisplay?.StopTracking(this, rulesetConfig);
rulesetConfig = null;
}
}
} }
/// <summary> /// <summary>

View File

@ -21,7 +21,7 @@ namespace osu.Game.Rulesets.UI
public abstract class RulesetInputManager<T> : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler public abstract class RulesetInputManager<T> : PassThroughInputManager, ICanAttachKeyCounter, IHasReplayHandler
where T : struct where T : struct
{ {
public class RulesetKeyBindingContainer : DatabasedKeyBindingInputManager<T> public class RulesetKeyBindingContainer : DatabasedKeyBindingContainer<T>
{ {
public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) public RulesetKeyBindingContainer(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique)
: base(ruleset, variant, unique) : base(ruleset, variant, unique)
@ -136,9 +136,20 @@ namespace osu.Game.Rulesets.UI
int loops = 0; int loops = 0;
while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame) while (validState && requireMoreUpdateLoops && loops++ < max_catch_up_updates_per_frame)
{
if (!base.UpdateSubTree()) if (!base.UpdateSubTree())
return false; return false;
if (isAttached)
{
// When handling replay input, we need to consider the possibility of fast-forwarding, which may cause the clock to be updated
// to a point very far into the future, then playing a frame at that time. In such a case, lifetime MUST be updated before
// input is handled. This is why base.Update is not called from the derived Update when handling replay input, and is instead
// called manually at the correct time here.
base.Update();
}
}
return true; return true;
} }
@ -173,8 +184,11 @@ namespace osu.Game.Rulesets.UI
// to ensure that the its time is valid for our children before input is processed // to ensure that the its time is valid for our children before input is processed
Clock.ProcessFrame(); Clock.ProcessFrame();
// Process input if (!isAttached)
base.Update(); {
// For non-replay input handling, this provides equivalent input ordering as if Update was not overridden
base.Update();
}
} }
#endregion #endregion

View File

@ -41,6 +41,12 @@ namespace osu.Game.Screens.Play.BreaksOverlay
private readonly InfoContainer info; private readonly InfoContainer info;
private readonly ArrowsOverlay arrowsOverlay; private readonly ArrowsOverlay arrowsOverlay;
public BreakOverlay(bool letterboxing, ScoreProcessor scoreProcessor)
: this(letterboxing)
{
bindProcessor(scoreProcessor);
}
public BreakOverlay(bool letterboxing) public BreakOverlay(bool letterboxing)
{ {
this.letterboxing = letterboxing; this.letterboxing = letterboxing;
@ -148,7 +154,7 @@ namespace osu.Game.Screens.Play.BreaksOverlay
arrowsOverlay.Hide(); arrowsOverlay.Hide();
} }
public void BindProcessor(ScoreProcessor processor) private void bindProcessor(ScoreProcessor processor)
{ {
info.AccuracyDisplay.Current.BindTo(processor.Accuracy); info.AccuracyDisplay.Current.BindTo(processor.Accuracy);
info.GradeDisplay.Current.BindTo(processor.Rank); info.GradeDisplay.Current.BindTo(processor.Rank);

View File

@ -6,6 +6,8 @@ using osu.Framework.Configuration;
using osu.Framework.Graphics; using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers; using osu.Framework.Graphics.Containers;
using osu.Framework.Input; using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Configuration; using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface; using osu.Game.Graphics.UserInterface;
@ -39,7 +41,7 @@ namespace osu.Game.Screens.Play
private static bool hasShownNotificationOnce; private static bool hasShownNotificationOnce;
public HUDOverlay() public HUDOverlay(ScoreProcessor scoreProcessor, RulesetContainer rulesetContainer, DecoupleableInterpolatingFramedClock decoupledClock, WorkingBeatmap working, IAdjustableClock adjustableSourceClock)
{ {
RelativeSizeAxes = Axes.Both; RelativeSizeAxes = Axes.Both;
@ -59,6 +61,18 @@ namespace osu.Game.Screens.Play
ReplaySettingsOverlay = CreateReplaySettingsOverlay(), ReplaySettingsOverlay = CreateReplaySettingsOverlay(),
} }
}); });
BindProcessor(scoreProcessor);
BindRulesetContainer(rulesetContainer);
Progress.Objects = rulesetContainer.Objects;
Progress.AudioClock = decoupledClock;
Progress.AllowSeeking = rulesetContainer.HasReplayLoaded;
Progress.OnSeek = pos => decoupledClock.Seek(pos);
ModDisplay.Current.BindTo(working.Mods);
ReplaySettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock;
} }
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
@ -115,7 +129,7 @@ namespace osu.Game.Screens.Play
} }
} }
public virtual void BindRulesetContainer(RulesetContainer rulesetContainer) protected virtual void BindRulesetContainer(RulesetContainer rulesetContainer)
{ {
(rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter); (rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter);
@ -201,7 +215,7 @@ namespace osu.Game.Screens.Play
protected virtual ReplaySettingsOverlay CreateReplaySettingsOverlay() => new ReplaySettingsOverlay(); protected virtual ReplaySettingsOverlay CreateReplaySettingsOverlay() => new ReplaySettingsOverlay();
public virtual void BindProcessor(ScoreProcessor processor) protected virtual void BindProcessor(ScoreProcessor processor)
{ {
ScoreCounter?.Current.BindTo(processor.TotalScore); ScoreCounter?.Current.BindTo(processor.TotalScore);
AccuracyCounter?.Current.BindTo(processor.Accuracy); AccuracyCounter?.Current.BindTo(processor.Accuracy);

View File

@ -1,35 +1,35 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>. // Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE // Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Framework.Timing;
using osu.Game.Configuration;
using osu.Game.Rulesets;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Backgrounds;
using System; using System;
using System.Linq; using System.Linq;
using System.Threading.Tasks; using System.Threading.Tasks;
using osu.Framework.Threading; using osu.Framework.Allocation;
using osu.Game.Rulesets.Mods; using osu.Framework.Audio;
using osu.Game.Rulesets.Scoring;
using osu.Game.Screens.Ranking;
using osu.Framework.Audio.Sample; using osu.Framework.Audio.Sample;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor; using osu.Framework.Graphics.Cursor;
using osu.Framework.Input;
using osu.Framework.Logging;
using osu.Framework.Screens;
using osu.Framework.Threading;
using osu.Framework.Timing;
using osu.Game.Beatmaps; using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Graphics.Cursor; using osu.Game.Graphics.Cursor;
using osu.Game.Online.API; using osu.Game.Online.API;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Backgrounds;
using osu.Game.Screens.Play.BreaksOverlay; using osu.Game.Screens.Play.BreaksOverlay;
using osu.Game.Screens.Ranking;
using osu.Game.Storyboards.Drawables; using osu.Game.Storyboards.Drawables;
using OpenTK;
namespace osu.Game.Screens.Play namespace osu.Game.Screens.Play
{ {
@ -79,7 +79,6 @@ namespace osu.Game.Screens.Play
#endregion #endregion
private BreakOverlay breakOverlay;
private Container storyboardContainer; private Container storyboardContainer;
private DrawableStoryboard storyboard; private DrawableStoryboard storyboard;
@ -155,6 +154,8 @@ namespace osu.Game.Screens.Play
userAudioOffset.ValueChanged += v => offsetClock.Offset = v; userAudioOffset.ValueChanged += v => offsetClock.Offset = v;
userAudioOffset.TriggerChange(); userAudioOffset.TriggerChange();
scoreProcessor = RulesetContainer.CreateScoreProcessor();
Children = new Drawable[] Children = new Drawable[]
{ {
storyboardContainer = new Container storyboardContainer = new Container
@ -170,13 +171,12 @@ namespace osu.Game.Screens.Play
OnRetry = Restart, OnRetry = Restart,
OnQuit = Exit, OnQuit = Exit,
CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded, CheckCanPause = () => AllowPause && ValidForResume && !HasFailed && !RulesetContainer.HasReplayLoaded,
OnPause = () => { OnPause = () =>
{
pauseContainer.Retries = RestartCount; pauseContainer.Retries = RestartCount;
hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused; hudOverlay.KeyCounter.IsCounting = pauseContainer.IsPaused;
}, },
OnResume = () => { OnResume = () => hudOverlay.KeyCounter.IsCounting = true,
hudOverlay.KeyCounter.IsCounting = true;
},
Children = new Drawable[] Children = new Drawable[]
{ {
new Container new Container
@ -186,12 +186,12 @@ namespace osu.Game.Screens.Play
Child = RulesetContainer, Child = RulesetContainer,
}, },
new SkipButton(firstObjectTime) { AudioClock = decoupledClock }, new SkipButton(firstObjectTime) { AudioClock = decoupledClock },
hudOverlay = new HUDOverlay hudOverlay = new HUDOverlay(scoreProcessor, RulesetContainer, decoupledClock, working, adjustableSourceClock)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre Origin = Anchor.Centre
}, },
breakOverlay = new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks) new BreakOverlay(beatmap.BeatmapInfo.LetterboxInBreaks, scoreProcessor)
{ {
Anchor = Anchor.Centre, Anchor = Anchor.Centre,
Origin = Anchor.Centre, Origin = Anchor.Centre,
@ -207,7 +207,10 @@ namespace osu.Game.Screens.Play
}, },
new HotkeyRetryOverlay new HotkeyRetryOverlay
{ {
Action = () => { Action = () =>
{
if (!IsCurrentScreen) return;
//we want to hide the hitrenderer immediately (looks better). //we want to hide the hitrenderer immediately (looks better).
//we may be able to remove this once the mouse cursor trail is improved. //we may be able to remove this once the mouse cursor trail is improved.
RulesetContainer?.Hide(); RulesetContainer?.Hide();
@ -216,24 +219,9 @@ namespace osu.Game.Screens.Play
} }
}; };
scoreProcessor = RulesetContainer.CreateScoreProcessor();
if (showStoryboard) if (showStoryboard)
initializeStoryboard(false); initializeStoryboard(false);
hudOverlay.BindProcessor(scoreProcessor);
hudOverlay.BindRulesetContainer(RulesetContainer);
hudOverlay.Progress.Objects = RulesetContainer.Objects;
hudOverlay.Progress.AudioClock = decoupledClock;
hudOverlay.Progress.OnSeek = pos => decoupledClock.Seek(pos);
hudOverlay.ModDisplay.Current.BindTo(working.Mods);
breakOverlay.BindProcessor(scoreProcessor);
hudOverlay.ReplaySettingsOverlay.PlaybackSettings.AdjustableClock = adjustableSourceClock;
// Bind ScoreProcessor to ourselves // Bind ScoreProcessor to ourselves
scoreProcessor.AllJudged += onCompletion; scoreProcessor.AllJudged += onCompletion;
scoreProcessor.Failed += onFail; scoreProcessor.Failed += onFail;
@ -288,6 +276,8 @@ namespace osu.Game.Screens.Play
{ {
onCompletionEvent = Schedule(delegate onCompletionEvent = Schedule(delegate
{ {
if (!IsCurrentScreen) return;
var score = new Score var score = new Score
{ {
Beatmap = Beatmap.Value.BeatmapInfo, Beatmap = Beatmap.Value.BeatmapInfo,

View File

@ -13,6 +13,7 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics; using osu.Game.Graphics;
using osu.Game.Overlays; using osu.Game.Overlays;
using osu.Game.Overlays.Mods; using osu.Game.Overlays.Mods;
using osu.Game.Rulesets.Mods;
using osu.Game.Screens.Edit; using osu.Game.Screens.Edit;
using osu.Game.Screens.Play; using osu.Game.Screens.Play;
using osu.Game.Screens.Ranking; using osu.Game.Screens.Ranking;
@ -47,10 +48,13 @@ namespace osu.Game.Screens.Select
private SampleChannel sampleConfirm; private SampleChannel sampleConfirm;
[BackgroundDependencyLoader(true)] [BackgroundDependencyLoader(true)]
private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, DialogOverlay dialogOverlay) private void load(OsuColour colours, AudioManager audio, BeatmapManager beatmaps, DialogOverlay dialogOverlay, OsuGame game)
{ {
sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection"); sampleConfirm = audio.Sample.Get(@"SongSelect/confirm-selection");
if (game != null)
modSelect.SelectedMods.BindTo(game.SelectedMods);
Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue); Footer.AddButton(@"mods", colours.Yellow, modSelect, Key.F1, float.MaxValue);
BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1); BeatmapOptions.AddButton(@"Remove", @"from unplayed", FontAwesome.fa_times_circle_o, colours.Purple, null, Key.Number1);
@ -121,6 +125,9 @@ namespace osu.Game.Screens.Select
if (Beatmap.Value.Track != null) if (Beatmap.Value.Track != null)
Beatmap.Value.Track.Looping = false; Beatmap.Value.Track.Looping = false;
Beatmap.Value.Mods.UnbindBindings();
Beatmap.Value.Mods.Value = new Mod[] { };
return false; return false;
} }

View File

@ -181,7 +181,7 @@ namespace osu.Game.Screens.Select
[BackgroundDependencyLoader(permitNulls: true)] [BackgroundDependencyLoader(permitNulls: true)]
private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours) private void load(BeatmapManager beatmaps, AudioManager audio, DialogOverlay dialog, OsuGame osu, OsuColour colours)
{ {
dependencies.CacheAs<SongSelect>(this); dependencies.CacheAs(this);
if (Footer != null) if (Footer != null)
{ {

View File

@ -6,7 +6,7 @@ using osu.Framework.Platform;
namespace osu.Game.Screens.Tournament.Components namespace osu.Game.Screens.Tournament.Components
{ {
public class DrawingsConfigManager : ConfigManager<DrawingsConfig> public class DrawingsConfigManager : IniConfigManager<DrawingsConfig>
{ {
protected override string Filename => @"drawings.ini"; protected override string Filename => @"drawings.ini";

View File

@ -91,6 +91,10 @@
<Reference Include="Humanizer, Version=2.2.0.0, Culture=neutral, PublicKeyToken=979442b78dfc278e, processorArchitecture=MSIL"> <Reference Include="Humanizer, Version=2.2.0.0, Culture=neutral, PublicKeyToken=979442b78dfc278e, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\Humanizer.Core.2.2.0\lib\netstandard1.0\Humanizer.dll</HintPath> <HintPath>$(SolutionDir)\packages\Humanizer.Core.2.2.0\lib\netstandard1.0\Humanizer.dll</HintPath>
</Reference> </Reference>
<Reference Include="JetBrains.Annotations, Version=11.1.0.0, Culture=neutral, PublicKeyToken=1010a0d8d6380325, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\JetBrains.Annotations.11.1.0\lib\net20\JetBrains.Annotations.dll</HintPath>
<Private>True</Private>
</Reference>
<Reference Include="Microsoft.Data.Sqlite, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL"> <Reference Include="Microsoft.Data.Sqlite, Version=2.0.0.0, Culture=neutral, PublicKeyToken=adb9793829ddae60, processorArchitecture=MSIL">
<HintPath>$(SolutionDir)\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll</HintPath> <HintPath>$(SolutionDir)\packages\Microsoft.Data.Sqlite.Core.2.0.0\lib\netstandard2.0\Microsoft.Data.Sqlite.dll</HintPath>
</Reference> </Reference>
@ -265,10 +269,18 @@
<Compile Include="Beatmaps\Formats\JsonBeatmapDecoder.cs" /> <Compile Include="Beatmaps\Formats\JsonBeatmapDecoder.cs" />
<Compile Include="Beatmaps\Formats\LegacyDecoder.cs" /> <Compile Include="Beatmaps\Formats\LegacyDecoder.cs" />
<Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" /> <Compile Include="Beatmaps\Formats\LegacyStoryboardDecoder.cs" />
<Compile Include="Configuration\DatabasedSetting.cs" />
<Compile Include="Configuration\SettingsStore.cs" />
<Compile Include="Configuration\DatabasedConfigManager.cs" />
<Compile Include="Configuration\SpeedChangeVisualisationMethod.cs" /> <Compile Include="Configuration\SpeedChangeVisualisationMethod.cs" />
<Compile Include="Database\DatabaseContextFactory.cs" /> <Compile Include="Database\DatabaseContextFactory.cs" />
<Compile Include="Database\IHasPrimaryKey.cs" /> <Compile Include="Database\IHasPrimaryKey.cs" />
<Compile Include="Graphics\Textures\LargeTextureStore.cs" /> <Compile Include="Graphics\Textures\LargeTextureStore.cs" />
<Compile Include="Migrations\20180125143340_Settings.cs" />
<Compile Include="Migrations\20180125143340_Settings.Designer.cs">
<DependentUpon>20180125143340_Settings.cs</DependentUpon>
</Compile>
<Compile Include="Migrations\20180131154205_AddMuteBinding.cs" />
<Compile Include="Overlays\Profile\SupporterIcon.cs" /> <Compile Include="Overlays\Profile\SupporterIcon.cs" />
<Compile Include="Online\API\Requests\GetFriendsRequest.cs" /> <Compile Include="Online\API\Requests\GetFriendsRequest.cs" />
<Compile Include="Overlays\Settings\DangerousSettingsButton.cs" /> <Compile Include="Overlays\Settings\DangerousSettingsButton.cs" />
@ -314,6 +326,8 @@
<Compile Include="Overlays\Profile\Sections\Ranks\ScoreModsContainer.cs" /> <Compile Include="Overlays\Profile\Sections\Ranks\ScoreModsContainer.cs" />
<Compile Include="Overlays\Settings\Sections\Gameplay\ScrollingSettings.cs" /> <Compile Include="Overlays\Settings\Sections\Gameplay\ScrollingSettings.cs" />
<Compile Include="Overlays\Settings\Sections\Maintenance\DeleteAllBeatmapsDialog.cs" /> <Compile Include="Overlays\Settings\Sections\Maintenance\DeleteAllBeatmapsDialog.cs" />
<Compile Include="Rulesets\Configuration\IRulesetConfigManager.cs" />
<Compile Include="Rulesets\Configuration\RulesetConfigManager.cs" />
<Compile Include="Rulesets\Mods\IApplicableFailOverride.cs" /> <Compile Include="Rulesets\Mods\IApplicableFailOverride.cs" />
<Compile Include="Rulesets\Mods\IApplicableMod.cs" /> <Compile Include="Rulesets\Mods\IApplicableMod.cs" />
<Compile Include="Rulesets\Mods\IApplicableToBeatmapConverter.cs" /> <Compile Include="Rulesets\Mods\IApplicableToBeatmapConverter.cs" />
@ -433,8 +447,8 @@
<Compile Include="Graphics\UserInterface\Volume\VolumeControlReceptor.cs" /> <Compile Include="Graphics\UserInterface\Volume\VolumeControlReceptor.cs" />
<Compile Include="Graphics\UserInterface\Volume\VolumeMeter.cs" /> <Compile Include="Graphics\UserInterface\Volume\VolumeMeter.cs" />
<Compile Include="Input\Bindings\DatabasedKeyBinding.cs" /> <Compile Include="Input\Bindings\DatabasedKeyBinding.cs" />
<Compile Include="Input\Bindings\DatabasedKeyBindingInputManager.cs" /> <Compile Include="Input\Bindings\DatabasedKeyBindingContainer.cs" />
<Compile Include="Input\Bindings\GlobalKeyBindingInputManager.cs" /> <Compile Include="Input\Bindings\GlobalActionContainer.cs" />
<Compile Include="Input\Handlers\ReplayInputHandler.cs" /> <Compile Include="Input\Handlers\ReplayInputHandler.cs" />
<Compile Include="Input\KeyBindingStore.cs" /> <Compile Include="Input\KeyBindingStore.cs" />
<Compile Include="IO\FileInfo.cs" /> <Compile Include="IO\FileInfo.cs" />

View File

@ -47,6 +47,7 @@ Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/maste
<package id="Humanizer.Core.zh-CN" version="2.2.0" targetFramework="net461" /> <package id="Humanizer.Core.zh-CN" version="2.2.0" targetFramework="net461" />
<package id="Humanizer.Core.zh-Hans" version="2.2.0" targetFramework="net461" /> <package id="Humanizer.Core.zh-Hans" version="2.2.0" targetFramework="net461" />
<package id="Humanizer.Core.zh-Hant" version="2.2.0" targetFramework="net461" /> <package id="Humanizer.Core.zh-Hant" version="2.2.0" targetFramework="net461" />
<package id="JetBrains.Annotations" version="11.1.0" targetFramework="net461" />
<package id="Microsoft.CSharp" version="4.4.0" targetFramework="net461" /> <package id="Microsoft.CSharp" version="4.4.0" targetFramework="net461" />
<package id="Microsoft.Data.Sqlite.Core" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.Data.Sqlite.Core" version="2.0.0" targetFramework="net461" />
<package id="Microsoft.EntityFrameworkCore" version="2.0.0" targetFramework="net461" /> <package id="Microsoft.EntityFrameworkCore" version="2.0.0" targetFramework="net461" />