1
0
mirror of https://github.com/ppy/osu.git synced 2025-03-17 22:17:25 +08:00

Merge branch 'master' into master

This commit is contained in:
Dean Herbert 2017-08-22 12:35:36 +09:00 committed by GitHub
commit 881eecb528
84 changed files with 905 additions and 667 deletions

@ -1 +1 @@
Subproject commit 5c22092e590d589927962b8d0173dae5f9b1405c
Subproject commit f1527e5456cd228ddfb68cf6d56eb5d28dc360bf

View File

@ -17,8 +17,8 @@ namespace osu.Desktop.Deploy
{
internal static class Program
{
private const string nuget_path = @"packages\NuGet.CommandLine.3.5.0\tools\NuGet.exe";
private const string squirrel_path = @"packages\squirrel.windows.1.5.2\tools\Squirrel.exe";
private const string nuget_path = @"packages\NuGet.CommandLine.4.1.0\tools\NuGet.exe";
private const string squirrel_path = @"packages\squirrel.windows.1.7.5\tools\Squirrel.exe";
private const string msbuild_path = @"C:\Program Files (x86)\MSBuild\14.0\Bin\MSBuild.exe";
public static string StagingFolder = ConfigurationManager.AppSettings["StagingFolder"];

View File

@ -2,12 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.MathUtils;
using osu.Game.Screens.Play;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
namespace osu.Desktop.Tests.Visual
@ -41,38 +37,5 @@ namespace osu.Desktop.Tests.Visual
Add(kc);
}
private class TestSliderBar<T> : SliderBar<T> where T : struct
{
public Color4 Color
{
get { return Box.Colour; }
set { Box.Colour = value; }
}
public Color4 SelectionColor
{
get { return SelectionBox.Colour; }
set { SelectionBox.Colour = value; }
}
protected readonly Box SelectionBox;
protected readonly Box Box;
public TestSliderBar()
{
Children = new Drawable[]
{
Box = new Box { RelativeSizeAxes = Axes.Both },
SelectionBox = new Box { RelativeSizeAxes = Axes.Both }
};
}
protected override void UpdateValue(float value)
{
SelectionBox.ScaleTo(
new Vector2(value, 1),
300, Easing.OutQuint);
}
}
}
}

View File

@ -8,6 +8,7 @@ using osu.Desktop.Tests.Beatmaps;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Beatmaps;
@ -77,6 +78,8 @@ namespace osu.Desktop.Tests.Visual
public override ScoreProcessor CreateScoreProcessor() => new TestScoreProcessor();
public override PassThroughInputManager CreateInputManager() => new PassThroughInputManager();
protected override BeatmapConverter<TestHitObject> CreateBeatmapConverter() => new TestBeatmapConverter();
protected override Playfield<TestHitObject, TestJudgement> CreatePlayfield() => new TestPlayfield(scrollingAxes);

View File

@ -14,6 +14,11 @@ using osu.Game.Rulesets.Taiko.UI;
using OpenTK;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Beatmaps;
using osu.Desktop.Tests.Beatmaps;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets;
using osu.Game.Rulesets.Objects;
namespace osu.Desktop.Tests.Visual
{
@ -27,10 +32,11 @@ namespace osu.Desktop.Tests.Visual
protected override double TimePerAction => default_duration * 2;
private readonly Random rng = new Random(1337);
private readonly TaikoPlayfield playfield;
private readonly Container playfieldContainer;
private TaikoRulesetContainer rulesetContainer;
private Container playfieldContainer;
public TestCaseTaikoPlayfield()
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
AddStep("Hit!", () => addHitJudgement(false));
AddStep("Kiai hit", () => addHitJudgement(true));
@ -51,6 +57,25 @@ namespace osu.Desktop.Tests.Visual
AddStep("Height test 5", () => changePlayfieldSize(5));
AddStep("Reset height", () => changePlayfieldSize(6));
var controlPointInfo = new ControlPointInfo();
controlPointInfo.TimingPoints.Add(new TimingControlPoint());
WorkingBeatmap beatmap = new TestWorkingBeatmap(new Beatmap
{
HitObjects = new List<HitObject> { new CentreHit() },
BeatmapInfo = new BeatmapInfo
{
Difficulty = new BeatmapDifficulty(),
Metadata = new BeatmapMetadata
{
Artist = @"Unknown",
Title = @"Sample Beatmap",
Author = @"peppy",
},
},
ControlPointInfo = controlPointInfo
});
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
Add(playfieldContainer = new Container
@ -58,12 +83,9 @@ namespace osu.Desktop.Tests.Visual
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.X,
Height = TaikoPlayfield.DEFAULT_HEIGHT,
Height = 768,
Clock = new FramedClock(rateAdjustClock),
Children = new[]
{
playfield = new TaikoPlayfield()
}
Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap, true) }
});
}
@ -128,18 +150,18 @@ namespace osu.Desktop.Tests.Visual
}
};
playfield.OnJudgement(h);
rulesetContainer.Playfield.OnJudgement(h);
if (RNG.Next(10) == 0)
{
h.Judgement.SecondHit = true;
playfield.OnJudgement(h);
rulesetContainer.Playfield.OnJudgement(h);
}
}
private void addMissJudgement()
{
playfield.OnJudgement(new DrawableTestHit(new Hit())
rulesetContainer.Playfield.OnJudgement(new DrawableTestHit(new Hit())
{
Judgement = new TaikoJudgement
{
@ -151,22 +173,17 @@ namespace osu.Desktop.Tests.Visual
private void addBarLine(bool major, double delay = scroll_time)
{
BarLine bl = new BarLine
{
StartTime = playfield.Time.Current + delay,
ScrollTime = scroll_time
};
BarLine bl = new BarLine { StartTime = rulesetContainer.Playfield.Time.Current + delay };
playfield.AddBarLine(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
rulesetContainer.Playfield.Add(major ? new DrawableBarLineMajor(bl) : new DrawableBarLine(bl));
}
private void addSwell(double duration = default_duration)
{
playfield.Add(new DrawableSwell(new Swell
rulesetContainer.Playfield.Add(new DrawableSwell(new Swell
{
StartTime = playfield.Time.Current + scroll_time,
StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
Duration = duration,
ScrollTime = scroll_time
}));
}
@ -177,43 +194,40 @@ namespace osu.Desktop.Tests.Visual
var d = new DrumRoll
{
StartTime = playfield.Time.Current + scroll_time,
StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
IsStrong = strong,
Duration = duration,
ScrollTime = scroll_time,
};
playfield.Add(new DrawableDrumRoll(d));
rulesetContainer.Playfield.Add(new DrawableDrumRoll(d));
}
private void addCentreHit(bool strong)
{
Hit h = new Hit
{
StartTime = playfield.Time.Current + scroll_time,
ScrollTime = scroll_time,
StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
IsStrong = strong
};
if (strong)
playfield.Add(new DrawableCentreHitStrong(h));
rulesetContainer.Playfield.Add(new DrawableCentreHitStrong(h));
else
playfield.Add(new DrawableCentreHit(h));
rulesetContainer.Playfield.Add(new DrawableCentreHit(h));
}
private void addRimHit(bool strong)
{
Hit h = new Hit
{
StartTime = playfield.Time.Current + scroll_time,
ScrollTime = scroll_time,
StartTime = rulesetContainer.Playfield.Time.Current + scroll_time,
IsStrong = strong
};
if (strong)
playfield.Add(new DrawableRimHitStrong(h));
rulesetContainer.Playfield.Add(new DrawableRimHitStrong(h));
else
playfield.Add(new DrawableRimHit(h));
rulesetContainer.Playfield.Add(new DrawableRimHit(h));
}
private class DrawableTestHit : DrawableHitObject<TaikoHitObject, TaikoJudgement>

View File

@ -3,14 +3,14 @@
using System.ComponentModel;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Catch
{
public class CatchInputManager : DatabasedKeyBindingInputManager<CatchAction>
public class CatchInputManager : RulesetInputManager<CatchAction>
{
public CatchInputManager(RulesetInfo ruleset)
: base(ruleset, simultaneousMode: SimultaneousBindingMode.Unique)
: base(ruleset, 0, SimultaneousBindingMode.Unique)
{
}
}

View File

@ -1,14 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Catch.Mods;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Scoring;
@ -23,12 +21,12 @@ namespace osu.Game.Rulesets.Catch
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(Key.Z, CatchAction.MoveLeft),
new KeyBinding(Key.Left, CatchAction.MoveLeft),
new KeyBinding(Key.X, CatchAction.MoveRight),
new KeyBinding(Key.Right, CatchAction.MoveRight),
new KeyBinding(Key.LShift, CatchAction.Dash),
new KeyBinding(Key.RShift, CatchAction.Dash),
new KeyBinding(InputKey.Z, CatchAction.MoveLeft),
new KeyBinding(InputKey.Left, CatchAction.MoveLeft),
new KeyBinding(InputKey.X, CatchAction.MoveRight),
new KeyBinding(InputKey.Right, CatchAction.MoveRight),
new KeyBinding(InputKey.Shift, CatchAction.Dash),
new KeyBinding(InputKey.Shift, CatchAction.Dash),
};
public override IEnumerable<Mod> GetModsFor(ModType type)
@ -101,13 +99,6 @@ namespace osu.Game.Rulesets.Catch
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_fruits_o };
public override IEnumerable<KeyCounter> CreateGameplayKeys() => new KeyCounter[]
{
new KeyCounterKeyboard(Key.ShiftLeft),
new KeyCounterMouse(MouseButton.Left),
new KeyCounterMouse(MouseButton.Right)
};
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new CatchDifficultyCalculator(beatmap);
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor();

View File

@ -28,7 +28,7 @@ namespace osu.Game.Rulesets.Catch.UI
protected override Playfield<CatchBaseHit, CatchJudgement> CreatePlayfield() => new CatchPlayfield();
public override PassThroughInputManager CreateKeyBindingInputManager() => new CatchInputManager(Ruleset?.RulesetInfo);
public override PassThroughInputManager CreateInputManager() => new CatchInputManager(Ruleset.RulesetInfo);
protected override DrawableHitObject<CatchBaseHit, CatchJudgement> GetVisualRepresentation(CatchBaseHit h)
{

View File

@ -47,7 +47,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
// The true distance, accounting for any repeats
double distance = (distanceData?.Distance ?? 0) * repeatCount;
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier / (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier);
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier / timingPoint.BeatLength;
// The duration of the osu! hit object
double osuDuration = distance / osuVelocity;

View File

@ -0,0 +1,21 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania
{
public class ManiaInputManager : RulesetInputManager<ManiaAction>
{
public ManiaInputManager(RulesetInfo ruleset)
: base(ruleset, 0, SimultaneousBindingMode.Unique)
{
}
}
public enum ManiaAction
{
// placeholder
}
}

View File

@ -6,7 +6,6 @@ using osu.Game.Rulesets.Mania.Mods;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Graphics;
@ -111,8 +110,6 @@ namespace osu.Game.Rulesets.Mania
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_mania_o };
public override IEnumerable<KeyCounter> CreateGameplayKeys() => new KeyCounter[] { /* Todo: Should be keymod specific */ };
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new ManiaDifficultyCalculator(beatmap);
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor();

View File

@ -10,6 +10,7 @@ using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Lists;
using osu.Framework.MathUtils;
using osu.Game.Beatmaps;
@ -92,6 +93,8 @@ namespace osu.Game.Rulesets.Mania.UI
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo);
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter();
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)

View File

@ -79,6 +79,7 @@
<Compile Include="Objects\ManiaHitObject.cs" />
<Compile Include="Objects\Note.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="ManiaInputManager.cs" />
<Compile Include="Timing\GravityScrollingContainer.cs" />
<Compile Include="Timing\ScrollingAlgorithm.cs" />
<Compile Include="UI\Column.cs" />

View File

@ -51,6 +51,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected virtual void UpdateCurrentState(ArmedState state)
{
}
private OsuInputManager osuActionInputManager;
internal OsuInputManager OsuActionInputManager => osuActionInputManager ?? (osuActionInputManager = GetContainingInputManager() as OsuInputManager);
}
public enum ComboResult

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Objects.Drawables;
@ -165,6 +166,13 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
glow.Colour = colours.BlueDark;
}
protected override void Update()
{
disc.Tracking = OsuActionInputManager.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton);
base.Update();
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();

View File

@ -1,10 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
@ -96,11 +98,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
return base.OnMouseMove(state);
}
// If the current time is between the start and end of the slider, we should track mouse input regardless of the cursor position.
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => canCurrentlyTrack || base.ReceiveMouseInputAt(screenSpacePos);
private bool tracking;
public bool Tracking
{
get { return tracking; }
set
private set
{
if (value == tracking) return;
@ -117,8 +122,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
base.Update();
// Make sure to use the base version of ReceiveMouseInputAt so that we correctly check the position.
if (Time.Current < slider.EndTime)
Tracking = canCurrentlyTrack && lastState != null && ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) && lastState.Mouse.HasMainButtonPressed;
Tracking = canCurrentlyTrack && lastState != null && base.ReceiveMouseInputAt(lastState.Mouse.NativeState.Position) && ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
}
public void UpdateProgress(double progress, int repeat)

View File

@ -66,21 +66,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
}
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
Tracking |= state.Mouse.HasMainButtonPressed;
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
Tracking &= state.Mouse.HasMainButtonPressed;
return base.OnMouseUp(state, args);
}
protected override bool OnMouseMove(InputState state)
{
Tracking |= state.Mouse.HasMainButtonPressed;
mousePosition = Parent.ToLocalSpace(state.Mouse.NativeState.Position);
return base.OnMouseMove(state);
}

View File

@ -69,7 +69,7 @@ namespace osu.Game.Rulesets.Osu.Objects
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier;
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
TickDistance = scoringDistance / difficulty.SliderTickRate;

View File

@ -2,36 +2,15 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
using System.Linq;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using OpenTK.Input;
using KeyboardState = osu.Framework.Input.KeyboardState;
using MouseState = osu.Framework.Input.MouseState;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Osu
{
public class OsuInputManager : DatabasedKeyBindingInputManager<OsuAction>
public class OsuInputManager : RulesetInputManager<OsuAction>
{
public OsuInputManager(RulesetInfo ruleset) : base(ruleset, simultaneousMode: SimultaneousBindingMode.Unique)
public OsuInputManager(RulesetInfo ruleset) : base(ruleset, 0, SimultaneousBindingMode.Unique)
{
}
protected override void TransformState(InputState state)
{
base.TransformState(state);
var mouse = state.Mouse as MouseState;
var keyboard = state.Keyboard as KeyboardState;
if (mouse != null && keyboard != null)
{
if (mouse.IsPressed(MouseButton.Left))
keyboard.Keys = keyboard.Keys.Concat(new[] { Key.LastKey + 1 });
if (mouse.IsPressed(MouseButton.Right))
keyboard.Keys = keyboard.Keys.Concat(new[] { Key.LastKey + 2 });
}
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
@ -10,7 +9,6 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.OsuDifficulty;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Graphics;
@ -27,10 +25,10 @@ namespace osu.Game.Rulesets.Osu
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(Key.Z, OsuAction.LeftButton),
new KeyBinding(Key.X, OsuAction.RightButton),
new KeyBinding(Key.LastKey + 1, OsuAction.LeftButton),
new KeyBinding(Key.LastKey + 2, OsuAction.RightButton),
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
new KeyBinding(InputKey.X, OsuAction.RightButton),
new KeyBinding(InputKey.MouseLeft, OsuAction.LeftButton),
new KeyBinding(InputKey.MouseRight, OsuAction.RightButton),
};
public override IEnumerable<BeatmapStatistic> GetBeatmapStatistics(WorkingBeatmap beatmap) => new[]
@ -122,14 +120,6 @@ namespace osu.Game.Rulesets.Osu
public override string Description => "osu!";
public override IEnumerable<KeyCounter> CreateGameplayKeys() => new KeyCounter[]
{
new KeyCounterKeyboard(Key.Z),
new KeyCounterKeyboard(Key.X),
new KeyCounterMouse(MouseButton.Left),
new KeyCounterMouse(MouseButton.Right)
};
public override ScoreProcessor CreateScoreProcessor() => new OsuScoreProcessor();
public override SettingsSubsection CreateSettings() => new OsuSettings();

View File

@ -31,7 +31,7 @@ namespace osu.Game.Rulesets.Osu.UI
protected override Playfield<OsuHitObject, OsuJudgement> CreatePlayfield() => new OsuPlayfield();
public override PassThroughInputManager CreateKeyBindingInputManager() => new OsuInputManager(Ruleset?.RulesetInfo);
public override PassThroughInputManager CreateInputManager() => new OsuInputManager(Ruleset.RulesetInfo);
protected override DrawableHitObject<OsuHitObject, OsuJudgement> GetVisualRepresentation(OsuHitObject h)
{

View File

@ -39,10 +39,14 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
/// </summary>
private const float taiko_base_distance = 100;
private bool isForCurrentRuleset;
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(HitObject) };
protected override Beatmap<TaikoHitObject> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset)
{
this.isForCurrentRuleset = isForCurrentRuleset;
// Rewrite the beatmap info to add the slider velocity multiplier
BeatmapInfo info = original.BeatmapInfo.DeepClone();
info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier;
@ -81,7 +85,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
DifficultyControlPoint difficultyPoint = beatmap.ControlPointInfo.DifficultyPointAt(obj.StartTime);
double speedAdjustment = difficultyPoint.SpeedMultiplier;
double speedAdjustedBeatLength = timingPoint.BeatLength * speedAdjustment;
double speedAdjustedBeatLength = timingPoint.BeatLength / speedAdjustment;
// The true distance, accounting for any repeats. This ends up being the drum roll distance later
double distance = distanceData.Distance * repeats * legacy_velocity_multiplier;
@ -94,7 +98,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
// For some reason, old osu! always uses speedAdjustment to determine the taiko velocity, but
// only uses it to determine osu! velocity if beatmap version < 8. Let's account for that here.
if (beatmap.BeatmapInfo.BeatmapVersion >= 8)
speedAdjustedBeatLength /= speedAdjustment;
speedAdjustedBeatLength *= speedAdjustment;
// The velocity of the osu! hit object - calculated as the velocity of a slider
double osuVelocity = osu_base_scoring_distance * beatmap.BeatmapInfo.Difficulty.SliderMultiplier * legacy_velocity_multiplier / speedAdjustedBeatLength;
@ -104,7 +108,7 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
// If the drum roll is to be split into hit circles, assume the ticks are 1/8 spaced within the duration of one beat
double tickSpacing = Math.Min(speedAdjustedBeatLength / beatmap.BeatmapInfo.Difficulty.SliderTickRate, taikoDuration / repeats);
if (tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
if (!isForCurrentRuleset && tickSpacing > 0 && osuDuration < 2 * speedAdjustedBeatLength)
{
List<SampleInfoList> allSamples = curveData != null ? curveData.RepeatSamples : new List<SampleInfoList>(new[] { samples });

View File

@ -2,16 +2,17 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using OpenTK;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
/// <summary>
/// A line that scrolls alongside hit objects in the playfield and visualises control points.
/// </summary>
public class DrawableBarLine : Container
public class DrawableBarLine : DrawableScrollingHitObject<TaikoHitObject, TaikoJudgement>
{
/// <summary>
/// The width of the line tracker.
@ -34,15 +35,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected readonly BarLine BarLine;
public DrawableBarLine(BarLine barLine)
: base(barLine)
{
BarLine = barLine;
Anchor = Anchor.CentreLeft;
Origin = Anchor.Centre;
RelativePositionAxes = Axes.X;
RelativeSizeAxes = Axes.Y;
Width = tracker_width;
Children = new[]
@ -56,24 +56,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Alpha = 0.75f
}
};
LifetimeStart = BarLine.StartTime - BarLine.ScrollTime * 2;
LifetimeEnd = BarLine.StartTime + BarLine.ScrollTime;
}
protected override void LoadComplete()
protected override TaikoJudgement CreateJudgement() => null;
protected override void UpdateState(ArmedState state)
{
base.LoadComplete();
this.Delay(BarLine.StartTime - Time.Current).FadeOut(base_fadeout_time * BarLine.ScrollTime / 1000);
}
private void updateScrollPosition(double time) => this.MoveToX((float)((BarLine.StartTime - time) / BarLine.ScrollTime));
protected override void Update()
{
base.Update();
updateScrollPosition(Time.Current);
}
}
}
}

View File

@ -20,10 +20,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// </summary>
private const float triangle_size = 20f;
private readonly Container triangleContainer;
public DrawableBarLineMajor(BarLine barLine)
: base(barLine)
{
Add(new Container
Add(triangleContainer = new Container
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
@ -53,5 +55,13 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Tracker.Alpha = 1f;
}
protected override void LoadComplete()
{
base.LoadComplete();
using (triangleContainer.BeginAbsoluteSequence(HitObject.StartTime))
triangleContainer.FadeOut(150);
}
}
}
}

View File

@ -4,13 +4,12 @@
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableCentreHit : DrawableHit
{
protected override Key[] HitKeys { get; } = { Key.F, Key.J };
protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre };
public DrawableCentreHit(Hit hit)
: base(hit)

View File

@ -4,13 +4,12 @@
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableCentreHitStrong : DrawableHitStrong
{
protected override Key[] HitKeys { get; } = { Key.F, Key.J };
protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftCentre, TaikoAction.RightCentre };
public DrawableCentreHitStrong(Hit hit)
: base(hit)

View File

@ -11,6 +11,7 @@ using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@ -31,30 +32,31 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public DrawableDrumRoll(DrumRoll drumRoll)
: base(drumRoll)
{
RelativeSizeAxes = Axes.Y;
AutoSizeAxes = Axes.X;
Width = (float)HitObject.Duration;
Container<DrawableDrumRollTick> tickContainer;
MainPiece.Add(tickContainer = new Container<DrawableDrumRollTick>
{
RelativeSizeAxes = Axes.Both,
RelativeChildOffset = new Vector2((float)HitObject.StartTime, 0),
RelativeChildSize = new Vector2((float)HitObject.Duration, 1)
});
foreach (var tick in drumRoll.Ticks)
{
var newTick = new DrawableDrumRollTick(tick)
{
X = (float)((tick.StartTime - HitObject.StartTime) / HitObject.Duration)
};
var newTick = new DrawableDrumRollTick(tick);
newTick.OnJudgement += onTickJudgement;
AddNested(newTick);
MainPiece.Add(newTick);
tickContainer.Add(newTick);
}
}
protected override TaikoJudgement CreateJudgement() => new TaikoJudgement { SecondHit = HitObject.IsStrong };
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece
{
Length = (float)(HitObject.Duration / HitObject.ScrollTime),
PlayfieldLengthReference = () => Parent.DrawSize.X
};
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece();
public override bool OnPressed(TaikoAction action) => false;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
@ -63,17 +65,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
accentDarkColour = colours.YellowDarker;
}
protected override void LoadComplete()
{
base.LoadComplete();
// This is naive, however it's based on the reasoning that the hit target
// is further than mid point of the play field, so the time taken to scroll in should always
// be greater than the time taken to scroll out to the left of the screen.
// Thus, using PreEmpt here is enough for the drum roll to completely scroll out.
LifetimeEnd = HitObject.EndTime + HitObject.ScrollTime;
}
private void onTickJudgement(DrawableHitObject<TaikoHitObject, TaikoJudgement> obj)
{
if (obj.Judgement.Result == HitResult.Hit)

View File

@ -5,7 +5,6 @@ using System;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using OpenTK.Input;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@ -15,9 +14,21 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public DrawableDrumRollTick(DrumRollTick tick)
: base(tick)
{
// Because ticks aren't added by the ScrollingPlayfield, we need to set the following properties ourselves
RelativePositionAxes = Axes.X;
X = (float)tick.StartTime;
FillMode = FillMode.Fit;
}
protected override void LoadComplete()
{
base.LoadComplete();
// We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize
Width *= Parent.RelativeChildSize.X;
}
protected override TaikoPiece CreateMainPiece() => new TickPiece
{
Filled = HitObject.FirstTick
@ -47,12 +58,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
protected override void UpdateScrollPosition(double time)
{
// Ticks don't move
}
protected override bool HandleKeyPress(Key key)
public override bool OnPressed(TaikoAction action)
{
return Judgement.Result == HitResult.None && UpdateJudgement(true);
}

View File

@ -7,7 +7,6 @@ using osu.Framework.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@ -16,7 +15,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
/// <summary>
/// A list of keys which can result in hits for this HitObject.
/// </summary>
protected abstract Key[] HitKeys { get; }
protected abstract TaikoAction[] HitActions { get; }
/// <summary>
/// Whether the last key pressed is a valid hit key.
@ -29,6 +28,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
FillMode = FillMode.Fit;
}
protected override void LoadComplete()
{
base.LoadComplete();
// We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize
Width *= Parent.RelativeChildSize.X;
}
protected override void CheckJudgement(bool userTriggered)
{
if (!userTriggered)
@ -54,12 +61,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Judgement.Result = HitResult.Miss;
}
protected override bool HandleKeyPress(Key key)
public override bool OnPressed(TaikoAction action)
{
if (Judgement.Result != HitResult.None)
return false;
validKeyPressed = HitKeys.Contains(key);
validKeyPressed = HitActions.Contains(action);
return UpdateJudgement(true);
}

View File

@ -3,10 +3,8 @@
using System;
using System.Linq;
using osu.Framework.Input;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
@ -20,7 +18,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private double firstHitTime;
private bool firstKeyHeld;
private Key firstHitKey;
private TaikoAction firstHitAction;
protected DrawableHitStrong(Hit hit)
: base(hit)
@ -46,18 +44,26 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Judgement.SecondHit = true;
}
protected override bool HandleKeyPress(Key key)
public override bool OnReleased(TaikoAction action)
{
if (action == firstHitAction)
firstKeyHeld = false;
return base.OnReleased(action);
}
public override bool OnPressed(TaikoAction action)
{
// Check if we've handled the first key
if (Judgement.Result == HitResult.None)
{
// First key hasn't been handled yet, attempt to handle it
bool handled = base.HandleKeyPress(key);
bool handled = base.OnPressed(action);
if (handled)
{
firstHitTime = Time.Current;
firstHitKey = key;
firstHitAction = action;
firstKeyHeld = true;
}
return handled;
@ -68,22 +74,15 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return false;
// Don't handle represses of the first key
if (firstHitKey == key)
if (firstHitAction == action)
return false;
// Don't handle invalid hit key presses
if (!HitKeys.Contains(key))
// Don't handle invalid hit action presses
if (!HitActions.Contains(action))
return false;
// Assume the intention was to hit the strong hit with both keys only if the first key is still being held down
return firstKeyHeld && UpdateJudgement(true);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
firstKeyHeld = state.Keyboard.Keys.Contains(firstHitKey);
return base.OnKeyDown(state, args);
}
}
}

View File

@ -4,13 +4,12 @@
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableRimHit : DrawableHit
{
protected override Key[] HitKeys { get; } = { Key.D, Key.K };
protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim };
public DrawableRimHit(Hit hit)
: base(hit)

View File

@ -4,13 +4,12 @@
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public class DrawableRimHitStrong : DrawableHitStrong
{
protected override Key[] HitKeys { get; } = { Key.D, Key.K };
protected override TaikoAction[] HitActions { get; } = { TaikoAction.LeftRim, TaikoAction.RightRim };
public DrawableRimHitStrong(Hit hit)
: base(hit)

View File

@ -13,7 +13,6 @@ using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Graphics.Shapes;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
@ -35,9 +34,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
private readonly CircularContainer targetRing;
private readonly CircularContainer expandingRing;
private readonly Key[] rimKeys = { Key.D, Key.K };
private readonly Key[] centreKeys = { Key.F, Key.J };
private Key[] lastKeySet;
private readonly TaikoAction[] rimActions = { TaikoAction.LeftRim, TaikoAction.RightRim };
private readonly TaikoAction[] centreActions = { TaikoAction.LeftCentre, TaikoAction.RightCentre };
private TaikoAction[] lastAction;
/// <summary>
/// The amount of times the user has hit this swell.
@ -118,7 +117,6 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
});
MainPiece.Add(symbol = new SwellSymbolPiece());
}
[BackgroundDependencyLoader]
@ -129,6 +127,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
targetRing.BorderColour = colours.YellowDark.Opacity(0.25f);
}
protected override void LoadComplete()
{
base.LoadComplete();
// We need to set this here because RelativeSizeAxes won't/can't set our size by default with a different RelativeChildSize
Width *= Parent.RelativeChildSize.X;
}
protected override void CheckJudgement(bool userTriggered)
{
if (userTriggered)
@ -189,21 +195,22 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Expire();
}
protected override void UpdateScrollPosition(double time)
protected override void Update()
{
// Make the swell stop at the hit target
double t = Math.Min(HitObject.StartTime, time);
base.Update();
// Make the swell stop at the hit target
X = (float)Math.Max(Time.Current, HitObject.StartTime);
double t = Math.Min(HitObject.StartTime, Time.Current);
if (t == HitObject.StartTime && !hasStarted)
{
OnStart?.Invoke();
hasStarted = true;
}
base.UpdateScrollPosition(t);
}
protected override bool HandleKeyPress(Key key)
public override bool OnPressed(TaikoAction action)
{
if (Judgement.Result != HitResult.None)
return false;
@ -213,12 +220,12 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
return false;
// Find the keyset which this key corresponds to
var keySet = rimKeys.Contains(key) ? rimKeys : centreKeys;
var keySet = rimActions.Contains(action) ? rimActions : centreActions;
// Ensure alternating keysets
if (keySet == lastKeySet)
if (keySet == lastAction)
return false;
lastKeySet = keySet;
lastAction = keySet;
UpdateJudgement(true);

View File

@ -1,26 +1,19 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Taiko.Judgements;
using osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces;
using OpenTK;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
public abstract class DrawableTaikoHitObject<TaikoHitType> : DrawableHitObject<TaikoHitObject, TaikoJudgement>
where TaikoHitType : TaikoHitObject
public abstract class DrawableTaikoHitObject<TaikoHitType>
: DrawableScrollingHitObject<TaikoHitObject, TaikoJudgement>, IKeyBindingHandler<TaikoAction>
where TaikoHitType : TaikoHitObject
{
/// <summary>
/// A list of keys which this hit object will accept. These are the standard Taiko keys for now.
/// These should be moved to bindings later.
/// </summary>
private readonly List<Key> validKeys = new List<Key>(new[] { Key.D, Key.F, Key.J, Key.K });
public override Vector2 OriginPosition => new Vector2(DrawHeight / 2);
protected readonly TaikoPiece MainPiece;
@ -38,44 +31,16 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
RelativeSizeAxes = Axes.Both;
Size = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
RelativePositionAxes = Axes.X;
Add(MainPiece = CreateMainPiece());
MainPiece.KiaiMode = HitObject.Kiai;
LifetimeStart = HitObject.StartTime - HitObject.ScrollTime * 2;
}
protected override TaikoJudgement CreateJudgement() => new TaikoJudgement();
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
/// <summary>
/// Sets the scroll position of the DrawableHitObject relative to the offset between
/// a time value and the HitObject's StartTime.
/// </summary>
/// <param name="time"></param>
protected virtual void UpdateScrollPosition(double time) => X = (float)((HitObject.StartTime - time) / HitObject.ScrollTime);
public abstract bool OnPressed(TaikoAction action);
protected override void Update()
{
UpdateScrollPosition(Time.Current);
}
protected virtual bool HandleKeyPress(Key key) => false;
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
// Make sure we don't handle held-down keys
if (args.Repeat)
return false;
// Check if we've pressed a valid taiko key
if (!validKeys.Contains(args.Key))
return false;
// Handle it!
return HandleKeyPress(args.Key);
}
public virtual bool OnReleased(TaikoAction action) => false;
}
}

View File

@ -1,23 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using osu.Framework.Graphics;
namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
{
public class ElongatedCirclePiece : CirclePiece
{
/// <summary>
/// As we are being used to define the absolute size of hits, we need to be given a relative reference of our containing playfield container.
/// </summary>
public Func<float> PlayfieldLengthReference;
/// <summary>
/// The length of this piece as a multiple of the value returned by <see cref="PlayfieldLengthReference"/>
/// </summary>
public float Length;
public ElongatedCirclePiece()
{
RelativeSizeAxes = Axes.Y;
@ -35,7 +24,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables.Pieces
Right = padding,
};
Width = (PlayfieldLengthReference?.Invoke() ?? 0) * Length + DrawHeight;
Width = Parent.DrawSize.X + DrawHeight;
}
}
}

View File

@ -80,7 +80,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
ret.Add(new DrumRollTick
{
FirstTick = first,
ScrollTime = ScrollTime,
TickSpacing = tickSpacing,
StartTime = t,
IsStrong = IsStrong,

View File

@ -24,16 +24,6 @@ namespace osu.Game.Rulesets.Taiko.Objects
/// </summary>
public const float DEFAULT_STRONG_SIZE = DEFAULT_SIZE * STRONG_SCALE;
/// <summary>
/// The time taken from the initial (off-screen) spawn position to the centre of the hit target for a <see cref="TimingControlPoint.BeatLength"/> of 1000ms.
/// </summary>
private const double scroll_time = 6000;
/// <summary>
/// Our adjusted <see cref="scroll_time"/> taking into consideration local <see cref="TimingControlPoint.BeatLength"/> and other speed multipliers.
/// </summary>
public double ScrollTime;
/// <summary>
/// Whether this HitObject is a "strong" type.
/// Strong hit objects give more points for hitting the hit object with both keys.
@ -49,12 +39,8 @@ namespace osu.Game.Rulesets.Taiko.Objects
{
base.ApplyDefaults(controlPointInfo, difficulty);
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
EffectControlPoint effectPoint = controlPointInfo.EffectPointAt(StartTime);
ScrollTime = scroll_time * (timingPoint.BeatLength * difficultyPoint.SpeedMultiplier / 1000) / difficulty.SliderMultiplier;
Kiai |= effectPoint.KiaiMode;
}
}

View File

@ -0,0 +1,29 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.ComponentModel;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Taiko
{
public class TaikoInputManager : RulesetInputManager<TaikoAction>
{
public TaikoInputManager(RulesetInfo ruleset)
: base(ruleset, 0, SimultaneousBindingMode.Unique)
{
}
}
public enum TaikoAction
{
[Description("Left (Rim)")]
LeftRim,
[Description("Left (Centre)")]
LeftCentre,
[Description("Right (Centre)")]
RightCentre,
[Description("Right (Rim)")]
RightRim
}
}

View File

@ -1,18 +1,17 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Input;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Taiko.Mods;
using osu.Game.Rulesets.Taiko.UI;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Taiko.Scoring;
using osu.Framework.Input.Bindings;
namespace osu.Game.Rulesets.Taiko
{
@ -20,6 +19,18 @@ namespace osu.Game.Rulesets.Taiko
{
public override RulesetContainer CreateRulesetContainerWith(WorkingBeatmap beatmap, bool isForCurrentRuleset) => new TaikoRulesetContainer(this, beatmap, isForCurrentRuleset);
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0) => new[]
{
new KeyBinding(InputKey.D, TaikoAction.LeftRim),
new KeyBinding(InputKey.F, TaikoAction.LeftCentre),
new KeyBinding(InputKey.J, TaikoAction.RightCentre),
new KeyBinding(InputKey.K, TaikoAction.RightRim),
new KeyBinding(InputKey.MouseLeft, TaikoAction.LeftCentre),
new KeyBinding(InputKey.MouseLeft, TaikoAction.RightCentre),
new KeyBinding(InputKey.MouseRight, TaikoAction.LeftRim),
new KeyBinding(InputKey.MouseRight, TaikoAction.RightRim),
};
public override IEnumerable<Mod> GetModsFor(ModType type)
{
switch (type)
@ -90,14 +101,6 @@ namespace osu.Game.Rulesets.Taiko
public override Drawable CreateIcon() => new SpriteIcon { Icon = FontAwesome.fa_osu_taiko_o };
public override IEnumerable<KeyCounter> CreateGameplayKeys() => new KeyCounter[]
{
new KeyCounterKeyboard(Key.D),
new KeyCounterKeyboard(Key.F),
new KeyCounterKeyboard(Key.J),
new KeyCounterKeyboard(Key.K)
};
public override DifficultyCalculator CreateDifficultyCalculator(Beatmap beatmap) => new TaikoDifficultyCalculator(beatmap);
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor();

View File

@ -3,13 +3,12 @@
using System;
using OpenTK;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
namespace osu.Game.Rulesets.Taiko.UI
@ -36,8 +35,8 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.X,
X = -middle_split / 2,
RimKey = Key.D,
CentreKey = Key.F
RimAction = TaikoAction.LeftRim,
CentreAction = TaikoAction.LeftCentre
},
new TaikoHalfDrum(true)
{
@ -47,8 +46,8 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
RelativePositionAxes = Axes.X,
X = middle_split / 2,
RimKey = Key.K,
CentreKey = Key.J
RimAction = TaikoAction.RightRim,
CentreAction = TaikoAction.RightCentre
}
};
}
@ -56,17 +55,17 @@ namespace osu.Game.Rulesets.Taiko.UI
/// <summary>
/// A half-drum. Contains one centre and one rim hit.
/// </summary>
private class TaikoHalfDrum : Container
private class TaikoHalfDrum : Container, IKeyBindingHandler<TaikoAction>
{
/// <summary>
/// The key to be used for the rim of the half-drum.
/// </summary>
public Key RimKey;
public TaikoAction RimAction;
/// <summary>
/// The key to be used for the centre of the half-drum.
/// </summary>
public Key CentreKey;
public TaikoAction CentreAction;
private readonly Sprite rim;
private readonly Sprite rimHit;
@ -124,20 +123,17 @@ namespace osu.Game.Rulesets.Taiko.UI
centreHit.Colour = colours.Pink;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
public bool OnPressed(TaikoAction action)
{
if (args.Repeat)
return false;
Drawable target = null;
Drawable back = null;
if (args.Key == CentreKey)
if (action == CentreAction)
{
target = centreHit;
back = centre;
}
else if (args.Key == RimKey)
else if (action == RimAction)
{
target = rimHit;
back = rim;
@ -166,6 +162,8 @@ namespace osu.Game.Rulesets.Taiko.UI
return false;
}
public bool OnReleased(TaikoAction action) => false;
}
}
}

View File

@ -18,7 +18,7 @@ using osu.Game.Rulesets.Taiko.Objects.Drawables;
namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoPlayfield : Playfield<TaikoHitObject, TaikoJudgement>
public class TaikoPlayfield : ScrollingPlayfield<TaikoHitObject, TaikoJudgement>
{
/// <summary>
/// Default height of a <see cref="TaikoPlayfield"/> when inside a <see cref="TaikoRulesetContainer"/>.
@ -35,16 +35,18 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary>
private const float left_area_size = 240;
protected override Container<Drawable> Content => hitObjectContainer;
private readonly Container<HitExplosion> hitExplosionContainer;
private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
private readonly Container<DrawableBarLine> barLineContainer;
private readonly Container<DrawableTaikoJudgement> judgementContainer;
private readonly Container hitObjectContainer;
protected override Container<Drawable> Content => content;
private readonly Container content;
private readonly Container topLevelHitContainer;
private readonly Container barlineContainer;
private readonly Container overlayBackgroundContainer;
private readonly Container backgroundContainer;
@ -52,6 +54,7 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Box background;
public TaikoPlayfield()
: base(Axes.X)
{
AddRangeInternal(new Drawable[]
{
@ -84,7 +87,7 @@ namespace osu.Game.Rulesets.Taiko.UI
{
new Container
{
Name = "Masked elements",
Name = "Masked elements before hit objects",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
Masking = true,
@ -96,23 +99,27 @@ namespace osu.Game.Rulesets.Taiko.UI
FillMode = FillMode.Fit,
BlendingMode = BlendingMode.Additive,
},
barLineContainer = new Container<DrawableBarLine>
{
RelativeSizeAxes = Axes.Both,
},
new HitTarget
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit
},
hitObjectContainer = new Container
{
RelativeSizeAxes = Axes.Both,
},
}
}
},
barlineContainer = new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET }
},
content = new Container
{
Name = "Hit objects",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Left = HIT_TARGET_OFFSET },
Masking = true
},
kiaiExplosionContainer = new Container<KiaiHitExplosion>
{
Name = "Kiai hit explosions",
@ -181,6 +188,8 @@ namespace osu.Game.Rulesets.Taiko.UI
RelativeSizeAxes = Axes.Both,
}
});
VisibleTimeRange.Value = 6000;
}
[BackgroundDependencyLoader]
@ -199,17 +208,16 @@ namespace osu.Game.Rulesets.Taiko.UI
base.Add(h);
var barline = h as DrawableBarLine;
if (barline != null)
barlineContainer.Add(barline.CreateProxy());
// Swells should be moved at the very top of the playfield when they reach the hit target
var swell = h as DrawableSwell;
if (swell != null)
swell.OnStart += () => topLevelHitContainer.Add(swell.CreateProxy());
}
public void AddBarLine(DrawableBarLine barLine)
{
barLineContainer.Add(barLine);
}
public override void OnJudgement(DrawableHitObject<TaikoHitObject, TaikoJudgement> judgedObject)
{
bool wasHit = judgedObject.Judgement.Result == HitResult.Hit;
@ -230,7 +238,7 @@ namespace osu.Game.Rulesets.Taiko.UI
if (!secondHit)
{
if (judgedObject.X >= -0.05f && !(judgedObject is DrawableSwell))
if (judgedObject.X >= -0.05f && judgedObject is DrawableHit)
{
// If we're far enough away from the left stage, we should bring outselves in front of it
topLevelHitContainer.Add(judgedObject.CreateProxy());
@ -245,4 +253,4 @@ namespace osu.Game.Rulesets.Taiko.UI
hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();
}
}
}
}

View File

@ -18,10 +18,11 @@ using osu.Game.Rulesets.Taiko.Replays;
using OpenTK;
using osu.Game.Rulesets.Beatmaps;
using System.Linq;
using osu.Framework.Input;
namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoRulesetContainer : RulesetContainer<TaikoHitObject, TaikoJudgement>
public class TaikoRulesetContainer : ScrollingRulesetContainer<TaikoPlayfield, TaikoHitObject, TaikoJudgement>
{
public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap, bool isForCurrentRuleset)
: base(ruleset, beatmap, isForCurrentRuleset)
@ -36,11 +37,6 @@ namespace osu.Game.Rulesets.Taiko.UI
private void loadBarLines()
{
var taikoPlayfield = Playfield as TaikoPlayfield;
if (taikoPlayfield == null)
return;
TaikoHitObject lastObject = Beatmap.HitObjects[Beatmap.HitObjects.Count - 1];
double lastHitTime = 1 + (lastObject as IHasEndTime)?.EndTime ?? lastObject.StartTime;
@ -72,7 +68,7 @@ namespace osu.Game.Rulesets.Taiko.UI
barLine.ApplyDefaults(Beatmap.ControlPointInfo, Beatmap.BeatmapInfo.Difficulty);
bool isMajor = currentBeat % (int)currentPoint.TimeSignature == 0;
taikoPlayfield.AddBarLine(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
Playfield.Add(isMajor ? new DrawableBarLineMajor(barLine) : new DrawableBarLine(barLine));
double bl = currentPoint.BeatLength;
if (bl < 800)
@ -97,6 +93,8 @@ namespace osu.Game.Rulesets.Taiko.UI
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter();
public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
protected override Playfield<TaikoHitObject, TaikoJudgement> CreatePlayfield() => new TaikoPlayfield
{
Anchor = Anchor.CentreLeft,

View File

@ -86,6 +86,7 @@
<Compile Include="TaikoDifficultyCalculator.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Scoring\TaikoScoreProcessor.cs" />
<Compile Include="TaikoInputManager.cs" />
<Compile Include="UI\HitTarget.cs" />
<Compile Include="UI\InputDrum.cs" />
<Compile Include="UI\KiaiHitExplosion.cs" />

View File

@ -10,4 +10,4 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary>
public double SpeedMultiplier = 1;
}
}
}

View File

@ -10,7 +10,6 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
namespace osu.Game.Beatmaps
{
@ -76,8 +75,6 @@ namespace osu.Game.Beatmaps
public override string Description => "dummy";
public override IEnumerable<KeyCounter> CreateGameplayKeys() => new List<KeyCounter>();
public DummyRuleset(RulesetInfo rulesetInfo)
: base(rulesetInfo)
{

View File

@ -280,7 +280,7 @@ namespace osu.Game.Beatmaps.Formats
double time = double.Parse(split[0].Trim(), NumberFormatInfo.InvariantInfo);
double beatLength = double.Parse(split[1].Trim(), NumberFormatInfo.InvariantInfo);
double speedMultiplier = beatLength < 0 ? -beatLength / 100.0 : 1;
double speedMultiplier = beatLength < 0 ? 100.0 / -beatLength : 1;
TimeSignatures timeSignature = TimeSignatures.SimpleQuadruple;
if (split.Length >= 3)
@ -331,6 +331,7 @@ namespace osu.Game.Beatmaps.Formats
if (speedMultiplier != difficultyPoint.SpeedMultiplier)
{
beatmap.ControlPointInfo.DifficultyPoints.RemoveAll(x => x.Time == time);
beatmap.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint
{
Time = time,

View File

@ -44,19 +44,31 @@ namespace osu.Game.Graphics.UserInterface
{
List<Bar> bars = Children.ToList();
foreach (var bar in value.Select((length, index) => new { Value = length, Bar = bars.Count > index ? bars[index] : null }))
{
float length = MaxValue ?? value.Max();
if (length != 0)
length = bar.Value / length;
float size = value.Count();
if (size != 0)
size = 1.0f / size;
if (bar.Bar != null)
{
bar.Bar.Length = bar.Value / (MaxValue ?? value.Max());
bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / value.Count()) : new Vector2(1.0f / value.Count(), 1);
bar.Bar.Length = length;
bar.Bar.Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1);
}
else
{
Add(new Bar
{
RelativeSizeAxes = Axes.Both,
Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, 1.0f / value.Count()) : new Vector2(1.0f / value.Count(), 1),
Length = bar.Value / (MaxValue ?? value.Max()),
Size = (direction & BarDirection.Horizontal) > 0 ? new Vector2(1, size) : new Vector2(size, 1),
Length = length,
Direction = Direction,
});
}
}
//I'm using ToList() here because Where() returns an Enumerable which can change it's elements afterwards
RemoveRange(Children.Where((bar, index) => index >= value.Count()).ToList());
}

View File

@ -7,6 +7,7 @@ using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
@ -16,7 +17,7 @@ using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
public class OsuButton : Button
public class OsuButton : Button, IFilterable
{
private Box hover;
@ -108,5 +109,15 @@ namespace osu.Game.Graphics.UserInterface
Content.ScaleTo(1, 1000, Easing.OutElastic);
return base.OnMouseUp(state, args);
}
public string[] FilterTerms => new[] { Text };
public bool MatchingFilter
{
set
{
this.FadeTo(value ? 1 : 0);
}
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Input.Bindings;
@ -17,7 +18,7 @@ namespace osu.Game.Input.Bindings
{
private readonly RulesetInfo ruleset;
private readonly int variant;
private readonly int? variant;
private KeyBindingStore store;
@ -29,11 +30,14 @@ 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="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>
protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int variant = 0, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None)
protected DatabasedKeyBindingInputManager(RulesetInfo ruleset = null, int? variant = null, SimultaneousBindingMode simultaneousMode = SimultaneousBindingMode.None)
: base(simultaneousMode)
{
this.ruleset = ruleset;
this.variant = variant;
if (ruleset != null && variant == null)
throw new InvalidOperationException($"{nameof(variant)} can not be null when a non-null {nameof(ruleset)} is provided.");
}
[BackgroundDependencyLoader]

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Input;
using System.Collections.Generic;
using System.ComponentModel;
using System.Linq;
@ -22,16 +21,16 @@ namespace osu.Game.Input.Bindings
public override IEnumerable<KeyBinding> DefaultKeyBindings => new[]
{
new KeyBinding(Key.F8, GlobalAction.ToggleChat),
new KeyBinding(Key.F9, GlobalAction.ToggleSocial),
new KeyBinding(new[] { Key.LControl, Key.LAlt, Key.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { Key.LControl, Key.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { Key.LControl, Key.O }, GlobalAction.ToggleSettings),
new KeyBinding(new[] { Key.LControl, Key.D }, GlobalAction.ToggleDirect),
new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
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.O }, GlobalAction.ToggleSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.D }, GlobalAction.ToggleDirect),
};
protected override IEnumerable<Drawable> GetKeyboardInputQueue() =>
handler == null ? base.GetKeyboardInputQueue() : new[] { handler }.Concat(base.GetKeyboardInputQueue());
protected override IEnumerable<Drawable> KeyBindingInputQueue =>
handler == null ? base.KeyBindingInputQueue : new[] { handler }.Concat(base.KeyBindingInputQueue);
}
public enum GlobalAction

View File

@ -18,7 +18,7 @@ namespace osu.Game.Input
public KeyBindingStore(SQLiteConnection connection, RulesetStore rulesets, Storage storage = null)
: base(connection, storage)
{
foreach (var info in rulesets.Query<RulesetInfo>())
foreach (var info in rulesets.AllRulesets)
{
var ruleset = info.CreateInstance();
foreach (var variant in ruleset.AvailableVariants)

View File

@ -26,6 +26,8 @@ namespace osu.Game.Online.Chat
public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
private readonly List<LocalEchoMessage> pendingMessages = new List<LocalEchoMessage>();
public Bindable<bool> Joined = new Bindable<bool>();
public bool ReadOnly => Name != "#lazer";
@ -38,6 +40,16 @@ namespace osu.Game.Online.Chat
}
public event Action<IEnumerable<Message>> NewMessagesArrived;
public event Action<LocalEchoMessage, Message> PendingMessageResolved;
public event Action<Message> MessageRemoved;
public void AddLocalEcho(LocalEchoMessage message)
{
pendingMessages.Add(message);
Messages.Add(message);
NewMessagesArrived?.Invoke(new[] { message });
}
public void AddNewMessages(params Message[] messages)
{
@ -52,11 +64,42 @@ namespace osu.Game.Online.Chat
private void purgeOldMessages()
{
int messageCount = Messages.Count;
// never purge local echos
int messageCount = Messages.Count - pendingMessages.Count;
if (messageCount > MAX_HISTORY)
Messages.RemoveRange(0, messageCount - MAX_HISTORY);
}
/// <summary>
/// Replace or remove a message from the channel.
/// </summary>
/// <param name="echo">The local echo message (client-side).</param>
/// <param name="final">The response message, or null if the message became invalid.</param>
public void ReplaceMessage(LocalEchoMessage echo, Message final)
{
if (!pendingMessages.Remove(echo))
throw new InvalidOperationException("Attempted to remove echo that wasn't present");
Messages.Remove(echo);
if (final == null)
{
MessageRemoved?.Invoke(echo);
return;
}
if (Messages.Contains(final))
{
// message already inserted, so let's throw away this update.
// we may want to handle this better in the future, but for the time being api requests are single-threaded so order is assumed.
MessageRemoved?.Invoke(echo);
return;
}
Messages.Add(final);
PendingMessageResolved?.Invoke(echo, final);
}
public override string ToString() => Name;
}
}

View File

@ -0,0 +1,12 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Online.Chat
{
public class LocalEchoMessage : Message
{
public LocalEchoMessage() : base(null)
{
}
}
}

View File

@ -11,7 +11,7 @@ namespace osu.Game.Online.Chat
public class Message : IComparable<Message>, IEquatable<Message>
{
[JsonProperty(@"message_id")]
public readonly long Id;
public readonly long? Id;
//todo: this should be inside sender.
[JsonProperty(@"sender_id")]
@ -37,14 +37,22 @@ namespace osu.Game.Online.Chat
{
}
public Message(long id)
public Message(long? id)
{
Id = id;
}
public int CompareTo(Message other) => Id.CompareTo(other.Id);
public int CompareTo(Message other)
{
if (!Id.HasValue)
return other.Id.HasValue ? 1 : Timestamp.CompareTo(other.Timestamp);
if (!other.Id.HasValue)
return -1;
public bool Equals(Message other) => Id == other?.Id;
return Id.Value.CompareTo(other.Id.Value);
}
public virtual bool Equals(Message other) => Id == other?.Id;
public override int GetHashCode() => Id.GetHashCode();
}

View File

@ -182,7 +182,11 @@ namespace osu.Game
LoadComponentAsync(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
LoadComponentAsync(social = new SocialOverlay { Depth = -1 }, mainContent.Add);
LoadComponentAsync(chat = new ChatOverlay { Depth = -1 }, mainContent.Add);
LoadComponentAsync(settings = new MainSettings { Depth = -1 }, overlayContent.Add);
LoadComponentAsync(settings = new MainSettings
{
GetToolbarHeight = () => ToolbarOffset,
Depth = -1
}, overlayContent.Add);
LoadComponentAsync(userProfile = new UserProfileOverlay { Depth = -2 }, mainContent.Add);
LoadComponentAsync(musicController = new MusicController
{

View File

@ -94,13 +94,15 @@ namespace osu.Game
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent) =>
dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
private SQLiteConnection connection;
[BackgroundDependencyLoader]
private void load()
{
dependencies.Cache(this);
dependencies.Cache(LocalConfig);
SQLiteConnection connection = Host.Storage.GetDatabase(@"client");
connection = Host.Storage.GetDatabase(@"client");
connection.CreateTable<StoreVersion>();
@ -237,6 +239,8 @@ namespace osu.Game
LocalConfig.Save();
}
connection.Dispose();
base.Dispose(isDisposing);
}
}

View File

@ -2,26 +2,24 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Effects;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Allocation;
using osu.Game.Users;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat
{
public class ChatLine : Container
{
public readonly Message Message;
private static readonly Color4[] username_colours = {
private static readonly Color4[] username_colours =
{
OsuColour.FromHex("588c7e"),
OsuColour.FromHex("b2a367"),
OsuColour.FromHex("c98f65"),
@ -69,6 +67,8 @@ namespace osu.Game.Overlays.Chat
private Color4 customUsernameColour;
private OsuSpriteText timestamp;
public ChatLine(Message message)
{
Message = message;
@ -79,6 +79,26 @@ namespace osu.Game.Overlays.Chat
Padding = new MarginPadding { Left = padding, Right = padding };
}
private Message message;
private OsuSpriteText username;
private OsuTextFlowContainer contentFlow;
public Message Message
{
get { return message; }
set
{
if (message == value) return;
message = value;
if (!IsLoaded)
return;
updateMessageContent();
}
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, UserProfileOverlay profile)
{
@ -86,49 +106,54 @@ namespace osu.Game.Overlays.Chat
loadProfile = u => profile?.ShowUser(u);
}
private bool senderHasBackground => !string.IsNullOrEmpty(message.Sender.Colour);
protected override void LoadComplete()
{
base.LoadComplete();
bool hasBackground = !string.IsNullOrEmpty(Message.Sender.Colour);
Drawable username = new OsuSpriteText
bool hasBackground = senderHasBackground;
Drawable effectedUsername = username = new OsuSpriteText
{
Font = @"Exo2.0-BoldItalic",
Text = $@"{Message.Sender.Username}" + (hasBackground ? "" : ":"),
Colour = hasBackground ? customUsernameColour : username_colours[Message.UserId % username_colours.Length],
Colour = hasBackground ? customUsernameColour : username_colours[message.Sender.Id % username_colours.Length],
TextSize = text_size,
};
if (hasBackground)
{
// Background effect
username = username.WithEffect(new EdgeEffect
effectedUsername = new Container
{
AutoSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 4,
Parameters = new EdgeEffectParameters
{
Radius = 1,
Colour = OsuColour.FromHex(Message.Sender.Colour),
Type = EdgeEffectType.Shadow,
}
}, d =>
{
d.Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 };
d.Y = 3;
})
// Drop shadow effect
.WithEffect(new EdgeEffect
{
CornerRadius = 4,
Parameters = new EdgeEffectParameters
EdgeEffect = new EdgeEffectParameters
{
Roundness = 1,
Offset = new Vector2(0, 3),
Radius = 3,
Colour = Color4.Black.Opacity(0.3f),
Type = EdgeEffectType.Shadow,
},
// Drop shadow effect
Child = new Container
{
AutoSizeAxes = Axes.Both,
Masking = true,
CornerRadius = 4,
EdgeEffect = new EdgeEffectParameters
{
Radius = 1,
Colour = OsuColour.FromHex(message.Sender.Colour),
Type = EdgeEffectType.Shadow,
},
Padding = new MarginPadding { Left = 3, Right = 3, Bottom = 1, Top = -3 },
Y = 3,
Child = username,
}
});
};
}
Children = new Drawable[]
@ -138,23 +163,21 @@ namespace osu.Game.Overlays.Chat
Size = new Vector2(message_padding, text_size),
Children = new Drawable[]
{
new OsuSpriteText
timestamp = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Font = @"Exo2.0-SemiBold",
Text = $@"{Message.Timestamp.LocalDateTime:HH:mm:ss}",
FixedWidth = true,
TextSize = text_size * 0.75f,
Alpha = 0.4f,
},
new ClickableContainer
{
AutoSizeAxes = Axes.Both,
Origin = Anchor.TopRight,
Anchor = Anchor.TopRight,
Child = username,
Action = () => loadProfile(Message.Sender),
Child = effectedUsername,
Action = () => loadProfile(message.Sender),
},
}
},
@ -165,18 +188,27 @@ namespace osu.Game.Overlays.Chat
Padding = new MarginPadding { Left = message_padding + padding },
Children = new Drawable[]
{
new OsuTextFlowContainer(t =>
contentFlow = new OsuTextFlowContainer(t => { t.TextSize = text_size; })
{
t.TextSize = text_size;
})
{
Text = Message.Content,
AutoSizeAxes = Axes.Y,
RelativeSizeAxes = Axes.X,
}
}
}
};
updateMessageContent();
FinishTransforms(true);
}
private void updateMessageContent()
{
this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint);
timestamp.FadeTo(message is LocalEchoMessage ? 0 : 1, 500, Easing.OutQuint);
timestamp.Text = $@"{message.Timestamp.LocalDateTime:HH:mm:ss}";
username.Text = $@"{message.Sender.Username}" + (senderHasBackground ? "" : ":");
contentFlow.Text = message.Content;
}
}
}

View File

@ -3,7 +3,9 @@
using System;
using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -14,8 +16,19 @@ namespace osu.Game.Overlays.Chat
{
public class DrawableChannel : Container
{
private class ChatLineContainer : FillFlowContainer<ChatLine>
{
protected override int Compare(Drawable x, Drawable y)
{
var xC = (ChatLine)x;
var yC = (ChatLine)y;
return xC.Message.CompareTo(yC.Message);
}
}
public readonly Channel Channel;
private readonly FillFlowContainer<ChatLine> flow;
private readonly ChatLineContainer flow;
private readonly ScrollContainer scroll;
public DrawableChannel(Channel channel)
@ -32,20 +45,19 @@ namespace osu.Game.Overlays.Chat
// Some chat lines have effects that slightly protrude to the bottom,
// which we do not want to mask away, hence the padding.
Padding = new MarginPadding { Bottom = 5 },
Children = new Drawable[]
Child = flow = new ChatLineContainer
{
flow = new FillFlowContainer<ChatLine>
{
Direction = FillDirection.Vertical,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Padding = new MarginPadding { Left = 20, Right = 20 }
}
}
Padding = new MarginPadding { Left = 20, Right = 20 },
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Direction = FillDirection.Vertical,
},
}
};
channel.NewMessagesArrived += newMessagesArrived;
Channel.NewMessagesArrived += newMessagesArrived;
Channel.MessageRemoved += messageRemoved;
Channel.PendingMessageResolved += pendingMessageResolved;
}
[BackgroundDependencyLoader]
@ -63,14 +75,17 @@ namespace osu.Game.Overlays.Chat
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
Channel.NewMessagesArrived -= newMessagesArrived;
Channel.MessageRemoved -= messageRemoved;
Channel.PendingMessageResolved -= pendingMessageResolved;
}
private void newMessagesArrived(IEnumerable<Message> newMessages)
{
// Add up to last Channel.MAX_HISTORY messages
var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MAX_HISTORY));
//up to last Channel.MAX_HISTORY messages
flow.AddRange(displayMessages.Select(m => new ChatLine(m)));
if (!IsLoaded) return;
@ -90,6 +105,24 @@ namespace osu.Game.Overlays.Chat
}
}
private void pendingMessageResolved(Message existing, Message updated)
{
var found = flow.Children.LastOrDefault(c => c.Message == existing);
if (found != null)
{
Trace.Assert(updated.Id.HasValue, "An updated message was returned with no ID.");
flow.Remove(found);
found.Message = updated;
flow.Add(found);
}
}
private void messageRemoved(Message removed)
{
flow.Children.FirstOrDefault(c => c.Message == removed)?.FadeColour(Color4.Red, 400).FadeOut(600).Expire();
}
private void scrollToEnd() => ScheduleAfterChildren(() => scroll.ScrollToEnd());
}
}

View File

@ -6,23 +6,23 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Framework.Threading;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Graphics.Containers;
using osu.Game.Graphics.UserInterface;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
using osu.Game.Graphics.UserInterface;
using osu.Framework.Graphics.UserInterface;
using OpenTK.Graphics;
using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Overlays.Chat;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays
{
@ -37,7 +37,7 @@ namespace osu.Game.Overlays
private readonly LoadingAnimation loading;
private readonly FocusedTextBox inputTextBox;
private readonly FocusedTextBox textbox;
private APIAccess api;
@ -130,7 +130,7 @@ namespace osu.Game.Overlays
},
Children = new Drawable[]
{
inputTextBox = new FocusedTextBox
textbox = new FocusedTextBox
{
RelativeSizeAxes = Axes.Both,
Height = 1,
@ -175,7 +175,7 @@ namespace osu.Game.Overlays
if (state == Visibility.Visible)
{
inputTextBox.HoldFocus = false;
textbox.HoldFocus = false;
if (1f - chatHeight.Value < channel_selection_min_height)
{
chatContainer.ResizeHeightTo(1f - channel_selection_min_height, 800, Easing.OutQuint);
@ -186,7 +186,7 @@ namespace osu.Game.Overlays
}
else
{
inputTextBox.HoldFocus = true;
textbox.HoldFocus = true;
}
};
}
@ -242,8 +242,8 @@ namespace osu.Game.Overlays
protected override void OnFocus(InputState state)
{
//this is necessary as inputTextBox is masked away and therefore can't get focus :(
GetContainingInputManager().ChangeFocus(inputTextBox);
//this is necessary as textbox is masked away and therefore can't get focus :(
GetContainingInputManager().ChangeFocus(textbox);
base.OnFocus(state);
}
@ -252,7 +252,7 @@ namespace osu.Game.Overlays
this.MoveToY(0, transition_length, Easing.OutQuint);
this.FadeIn(transition_length, Easing.OutQuint);
inputTextBox.HoldFocus = true;
textbox.HoldFocus = true;
base.PopIn();
}
@ -261,7 +261,7 @@ namespace osu.Game.Overlays
this.MoveToY(Height, transition_length, Easing.InSine);
this.FadeOut(transition_length, Easing.InSine);
inputTextBox.HoldFocus = false;
textbox.HoldFocus = false;
base.PopOut();
}
@ -336,7 +336,7 @@ namespace osu.Game.Overlays
currentChannel = value;
inputTextBox.Current.Disabled = currentChannel.ReadOnly;
textbox.Current.Disabled = currentChannel.ReadOnly;
channelTabs.Current.Value = value;
var loaded = loadedChannels.Find(d => d.Channel == value);
@ -414,6 +414,7 @@ namespace osu.Game.Overlays
if (fetchReq != null) return;
fetchReq = new GetMessagesRequest(careChannels, lastMessageId);
fetchReq.Success += delegate (List<Message> messages)
{
foreach (var group in messages.Where(m => m.TargetType == TargetType.Channel).GroupBy(m => m.TargetId))
@ -424,6 +425,7 @@ namespace osu.Game.Overlays
Debug.Write("success!");
fetchReq = null;
};
fetchReq.Failure += delegate
{
Debug.Write("failure!");
@ -437,51 +439,42 @@ namespace osu.Game.Overlays
{
var postText = textbox.Text;
textbox.Text = string.Empty;
if (string.IsNullOrEmpty(postText))
return;
var target = currentChannel;
if (target == null) return;
if (!api.IsLoggedIn)
{
currentChannel?.AddNewMessages(new ErrorMessage("Please login to participate in chat!"));
textbox.Text = string.Empty;
target.AddNewMessages(new ErrorMessage("Please login to participate in chat!"));
return;
}
if (currentChannel == null) return;
if (postText[0] == '/')
{
// TODO: handle commands
currentChannel.AddNewMessages(new ErrorMessage("Chat commands are not supported yet!"));
textbox.Text = string.Empty;
target.AddNewMessages(new ErrorMessage("Chat commands are not supported yet!"));
return;
}
var message = new Message
var message = new LocalEchoMessage
{
Sender = api.LocalUser.Value,
Timestamp = DateTimeOffset.Now,
TargetType = TargetType.Channel, //TODO: read this from currentChannel
TargetId = currentChannel.Id,
TargetType = TargetType.Channel, //TODO: read this from channel
TargetId = target.Id,
Content = postText
};
textbox.ReadOnly = true;
var req = new PostMessageRequest(message);
req.Failure += e =>
{
textbox.FlashColour(Color4.Red, 1000);
textbox.ReadOnly = false;
};
req.Success += m =>
{
currentChannel.AddNewMessages(m);
textbox.ReadOnly = false;
textbox.Text = string.Empty;
};
target.AddLocalEcho(message);
req.Failure += e => target.ReplaceMessage(message, null);
req.Success += m => target.ReplaceMessage(message, m);
api.Queue(req);
}

View File

@ -11,6 +11,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Input;
@ -123,46 +124,74 @@ namespace osu.Game.Overlays.KeyBinding
base.OnHoverLost(state);
}
public override bool AcceptsFocus => true;
public override bool AcceptsFocus => bindTarget == null;
private KeyButton bindTarget;
protected override void OnFocus(InputState state)
{
AutoSizeDuration = 500;
AutoSizeEasing = Easing.OutQuint;
pressAKey.FadeIn(300, Easing.OutQuint);
pressAKey.Padding = new MarginPadding();
base.OnFocus(state);
}
public bool AllowMainMouseButtons;
private bool isModifier(Key k) => k < Key.F1;
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
switch (args.Key)
{
case Key.Escape:
GetContainingInputManager().ChangeFocus(null);
return true;
case Key.Delete:
bindTarget.UpdateKeyCombination(Key.Unknown);
store.Update(bindTarget.KeyBinding);
GetContainingInputManager().ChangeFocus(null);
return true;
}
protected override bool OnClick(InputState state) => true;
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
if (HasFocus)
{
bindTarget.UpdateKeyCombination(state.Keyboard.Keys.ToArray());
if (!isModifier(args.Key))
if (bindTarget.IsHovered)
{
if (!AllowMainMouseButtons)
{
switch (args.Button)
{
case MouseButton.Left:
case MouseButton.Right:
return true;
}
}
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
return true;
}
}
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
if (HasFocus && !state.Mouse.Buttons.Any())
{
if (bindTarget.IsHovered)
finalise();
else
updateBindTarget();
return true;
}
return base.OnKeyDown(state, args);
return base.OnMouseUp(state, args);
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (!HasFocus)
return false;
switch (args.Key)
{
case Key.Escape:
finalise();
return true;
case Key.Delete:
bindTarget.UpdateKeyCombination(InputKey.None);
finalise();
return true;
}
bindTarget.UpdateKeyCombination(KeyCombination.FromInputState(state));
if (!isModifier(args.Key)) finalise();
return true;
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
@ -178,27 +207,48 @@ namespace osu.Game.Overlays.KeyBinding
private void finalise()
{
store.Update(bindTarget.KeyBinding);
GetContainingInputManager().ChangeFocus(null);
if (bindTarget != null)
{
store.Update(bindTarget.KeyBinding);
bindTarget.IsBinding = false;
Schedule(() =>
{
// schedule to ensure we don't instantly get focus back on next OnMouseClick (see AcceptFocus impl.)
bindTarget = null;
});
}
if (HasFocus)
GetContainingInputManager().ChangeFocus(null);
pressAKey.FadeOut(300, Easing.OutQuint);
pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight };
}
protected override void OnFocus(InputState state)
{
AutoSizeDuration = 500;
AutoSizeEasing = Easing.OutQuint;
pressAKey.FadeIn(300, Easing.OutQuint);
pressAKey.Padding = new MarginPadding();
updateBindTarget();
base.OnFocus(state);
}
protected override void OnFocusLost(InputState state)
{
bindTarget.IsBinding = false;
bindTarget = null;
pressAKey.FadeOut(300, Easing.OutQuint);
pressAKey.Padding = new MarginPadding { Bottom = -pressAKey.DrawHeight };
finalise();
base.OnFocusLost(state);
}
protected override bool OnClick(InputState state)
private void updateBindTarget()
{
if (bindTarget != null) bindTarget.IsBinding = false;
bindTarget = buttons.FirstOrDefault(b => b.IsHovered) ?? buttons.FirstOrDefault();
if (bindTarget != null) bindTarget.IsBinding = true;
return bindTarget != null;
}
private class KeyButton : Container
@ -296,7 +346,7 @@ namespace osu.Game.Overlays.KeyBinding
}
}
public void UpdateKeyCombination(params Key[] newCombination)
public void UpdateKeyCombination(KeyCombination newCombination)
{
KeyBinding.KeyCombination = newCombination;
Text.Text = KeyBinding.KeyCombination.ReadableString();

View File

@ -40,7 +40,10 @@ namespace osu.Game.Overlays.KeyBinding
foreach (Enum v in Enum.GetValues(enumType))
// one row per valid action.
Add(new KeyBindingRow(v, bindings.Where(b => b.Action.Equals((int)(object)v))));
Add(new KeyBindingRow(v, bindings.Where(b => b.Action.Equals((int)(object)v)))
{
AllowMainMouseButtons = Ruleset != null
});
}
}
}

View File

@ -19,7 +19,7 @@ namespace osu.Game.Overlays
{
AddSection(new GlobalKeyBindingsSection(global, "Global"));
foreach (var ruleset in rulesets.Query<RulesetInfo>())
foreach (var ruleset in rulesets.AllRulesets)
AddSection(new RulesetBindingsSection(ruleset));
}

View File

@ -13,6 +13,7 @@ using osu.Game.Overlays.Settings;
using osu.Game.Overlays.Settings.Sections;
using osu.Game.Screens.Ranking;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Overlays
{
@ -40,27 +41,35 @@ namespace osu.Game.Overlays
public MainSettings()
: base(true)
{
keyBindingOverlay = new KeyBindingOverlay { Depth = 1 };
keyBindingOverlay = new KeyBindingOverlay
{
Depth = 1,
Anchor = Anchor.TopRight,
};
keyBindingOverlay.StateChanged += keyBindingOverlay_StateChanged;
}
public override bool AcceptsFocus => keyBindingOverlay.State != Visibility.Visible;
private const float hidden_width = 120;
private void keyBindingOverlay_StateChanged(VisibilityContainer container, Visibility visibility)
{
const float hidden_width = 120;
switch (visibility)
{
case Visibility.Visible:
Background.FadeTo(0.9f, 500, Easing.OutQuint);
SectionsContainer.FadeOut(100);
ContentContainer.MoveToX(hidden_width - ContentContainer.DrawWidth, 500, Easing.OutQuint);
Background.FadeTo(0.9f, 300, Easing.OutQuint);
Sidebar?.FadeColour(Color4.DarkGray, 300, Easing.OutQuint);
SectionsContainer.FadeOut(300, Easing.OutQuint);
ContentContainer.MoveToX(hidden_width - WIDTH, 500, Easing.OutQuint);
backButton.Delay(100).FadeIn(100);
break;
case Visibility.Hidden:
Background.FadeTo(0.6f, 500, Easing.OutQuint);
Sidebar?.FadeColour(Color4.White, 300, Easing.OutQuint);
SectionsContainer.FadeIn(500, Easing.OutQuint);
ContentContainer.MoveToX(0, 500, Easing.OutQuint);
@ -69,39 +78,26 @@ namespace osu.Game.Overlays
}
}
protected override void PopOut()
{
base.PopOut();
keyBindingOverlay.State = Visibility.Hidden;
}
protected override float ExpandedPosition => keyBindingOverlay.State == Visibility.Visible ? hidden_width - WIDTH : base.ExpandedPosition;
[BackgroundDependencyLoader]
private void load()
{
AddInternal(keyBindingOverlay);
AddInternal(backButton = new BackButton
ContentContainer.Add(keyBindingOverlay);
ContentContainer.Add(backButton = new BackButton
{
Alpha = 0,
Height = 150,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Width = hidden_width,
RelativeSizeAxes = Axes.Y,
Anchor = Anchor.CentreRight,
Origin = Anchor.CentreRight,
Action = () => keyBindingOverlay.Hide()
});
}
protected override void UpdateAfterChildren()
{
base.UpdateAfterChildren();
keyBindingOverlay.Margin = new MarginPadding { Left = ContentContainer.Margin.Left + ContentContainer.DrawWidth + ContentContainer.X };
backButton.Margin = new MarginPadding { Left = ContentContainer.Margin.Left };
backButton.Width = ContentContainer.DrawWidth + ContentContainer.X;
}
private class BackButton : OsuClickableContainer
{
private FillFlowContainer flow;
private AspectContainer aspect;
[BackgroundDependencyLoader]
@ -116,29 +112,22 @@ namespace osu.Game.Overlays
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
flow = new FillFlowContainer
new SpriteIcon
{
Anchor = Anchor.TopCentre,
RelativePositionAxes = Axes.Y,
Y = 0.4f,
AutoSizeAxes = Axes.Both,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Direction = FillDirection.Horizontal,
Children = new[]
{
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_left },
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_left },
new SpriteIcon { Size = new Vector2(15), Shadow = true, Icon = FontAwesome.fa_chevron_left },
}
Y = -15,
Size = new Vector2(15),
Shadow = true,
Icon = FontAwesome.fa_chevron_left
},
new OsuSpriteText
{
Anchor = Anchor.TopCentre,
RelativePositionAxes = Axes.Y,
Y = 0.7f,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Y = 15,
TextSize = 12,
Font = @"Exo2.0-Bold",
Origin = Anchor.Centre,
Text = @"back",
},
}
@ -146,18 +135,6 @@ namespace osu.Game.Overlays
};
}
protected override bool OnHover(InputState state)
{
flow.TransformSpacingTo(new Vector2(5), 500, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
flow.TransformSpacingTo(new Vector2(0), 500, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
aspect.ScaleTo(0.75f, 2000, Easing.OutQuint);
@ -171,4 +148,4 @@ namespace osu.Game.Overlays
}
}
}
}
}

View File

@ -258,7 +258,10 @@ namespace osu.Game.Overlays
Type = EdgeEffectType.Glow,
Radius = 8,
};
}
protected override void LoadComplete()
{
updateGlow();
FinishTransforms(true);
}

View File

@ -171,7 +171,7 @@ namespace osu.Game.Overlays.Settings.Sections.General
break;
}
if (form != null) GetContainingInputManager().ChangeFocus(form);
if (form != null) GetContainingInputManager()?.ChangeFocus(form);
}
public override bool AcceptsFocus => true;

View File

@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
@ -25,7 +26,7 @@ namespace osu.Game.Overlays
public const float SIDEBAR_WIDTH = Sidebar.DEFAULT_WIDTH;
private const float width = 400;
protected const float WIDTH = 400;
private const float sidebar_padding = 10;
@ -33,14 +34,17 @@ namespace osu.Game.Overlays
protected override Container<Drawable> Content => ContentContainer;
private Sidebar sidebar;
protected Sidebar Sidebar;
private SidebarButton selectedSidebarButton;
protected SettingsSectionsContainer SectionsContainer;
private SearchTextBox searchTextBox;
private Func<float> getToolbarHeight;
/// <summary>
/// Provide a source for the toolbar height.
/// </summary>
public Func<float> GetToolbarHeight;
private readonly bool showSidebar;
@ -55,23 +59,27 @@ namespace osu.Game.Overlays
protected virtual IEnumerable<SettingsSection> CreateSections() => null;
[BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuGame game)
[BackgroundDependencyLoader]
private void load()
{
InternalChild = ContentContainer = new Container
{
Width = width,
Width = WIDTH,
RelativeSizeAxes = Axes.Y,
Children = new Drawable[]
{
Background = new Box
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
Scale = new Vector2(2, 1), // over-extend to the left for transitions
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black,
Alpha = 0.6f,
},
SectionsContainer = new SettingsSectionsContainer
{
Masking = true,
RelativeSizeAxes = Axes.Both,
ExpandableHeader = CreateHeader(),
FixedHeader = searchTextBox = new SearchTextBox
@ -94,20 +102,18 @@ namespace osu.Game.Overlays
if (showSidebar)
{
AddInternal(sidebar = new Sidebar { Width = SIDEBAR_WIDTH });
AddInternal(Sidebar = new Sidebar { Width = SIDEBAR_WIDTH });
SectionsContainer.SelectedSection.ValueChanged += section =>
{
selectedSidebarButton.Selected = false;
selectedSidebarButton = sidebar.Children.Single(b => b.Section == section);
selectedSidebarButton = Sidebar.Children.Single(b => b.Section == section);
selectedSidebarButton.Selected = true;
};
}
searchTextBox.Current.ValueChanged += newValue => SectionsContainer.SearchContainer.SearchTerm = newValue;
getToolbarHeight = () => game?.ToolbarOffset ?? 0;
CreateSections()?.ForEach(AddSection);
}
@ -115,7 +121,7 @@ namespace osu.Game.Overlays
{
SectionsContainer.Add(section);
if (sidebar != null)
if (Sidebar != null)
{
var button = new SidebarButton
{
@ -123,15 +129,15 @@ namespace osu.Game.Overlays
Action = s =>
{
SectionsContainer.ScrollTo(s);
sidebar.State = ExpandedState.Contracted;
Sidebar.State = ExpandedState.Contracted;
},
};
sidebar.Add(button);
Sidebar.Add(button);
if (selectedSidebarButton == null)
{
selectedSidebarButton = sidebar.Children.First();
selectedSidebarButton = Sidebar.Children.First();
selectedSidebarButton.Selected = true;
}
}
@ -145,20 +151,24 @@ namespace osu.Game.Overlays
{
base.PopIn();
ContentContainer.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(1, TRANSITION_LENGTH / 2);
ContentContainer.MoveToX(ExpandedPosition, TRANSITION_LENGTH, Easing.OutQuint);
Sidebar?.MoveToX(0, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(1, TRANSITION_LENGTH, Easing.OutQuint);
searchTextBox.HoldFocus = true;
}
protected virtual float ExpandedPosition => 0;
protected override void PopOut()
{
base.PopOut();
ContentContainer.MoveToX(-width, TRANSITION_LENGTH, Easing.OutQuint);
sidebar?.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(0, TRANSITION_LENGTH / 2);
ContentContainer.MoveToX(-WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
Sidebar?.MoveToX(-SIDEBAR_WIDTH, TRANSITION_LENGTH, Easing.OutQuint);
this.FadeTo(0, TRANSITION_LENGTH, Easing.OutQuint);
searchTextBox.HoldFocus = false;
if (searchTextBox.HasFocus)
@ -179,8 +189,8 @@ namespace osu.Game.Overlays
{
base.UpdateAfterChildren();
ContentContainer.Margin = new MarginPadding { Left = sidebar?.DrawWidth ?? 0 };
ContentContainer.Padding = new MarginPadding { Top = getToolbarHeight() };
ContentContainer.Margin = new MarginPadding { Left = Sidebar?.DrawWidth ?? 0 };
ContentContainer.Padding = new MarginPadding { Top = GetToolbarHeight?.Invoke() ?? 0 };
}
protected class SettingsSectionsContainer : SectionsContainer<SettingsSection>

View File

@ -58,8 +58,8 @@ namespace osu.Game.Overlays.Toolbar
AutoSizeAxes = Axes.X,
Children = new Drawable[]
{
new ToolbarSocialButton(),
new ToolbarChatButton(),
new ToolbarSocialButton(),
new ToolbarMusicButton(),
new ToolbarButton
{

View File

@ -106,6 +106,9 @@ namespace osu.Game.Rulesets.Objects.Drawables
/// <returns>Whether a hit was processed.</returns>
protected bool UpdateJudgement(bool userTriggered)
{
if (Judgement == null)
return false;
var partial = Judgement as IPartialJudgement;
// Never re-process non-partial hits

View File

@ -32,6 +32,8 @@ namespace osu.Game.Rulesets.Objects.Drawables
}
}
public override bool RemoveWhenNotAlive => false;
protected DrawableScrollingHitObject(TObject hitObject)
: base(hitObject)
{

View File

@ -53,7 +53,7 @@ namespace osu.Game.Rulesets.Objects.Legacy
TimingControlPoint timingPoint = controlPointInfo.TimingPointAt(StartTime);
DifficultyControlPoint difficultyPoint = controlPointInfo.DifficultyPointAt(StartTime);
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier / difficultyPoint.SpeedMultiplier;
double scoringDistance = base_scoring_distance * difficulty.SliderMultiplier * difficultyPoint.SpeedMultiplier;
Velocity = scoringDistance / timingPoint.BeatLength;
}

View File

@ -5,7 +5,6 @@ using osu.Game.Beatmaps;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Play;
using System.Collections.Generic;
using osu.Framework.Graphics;
using osu.Framework.Input.Bindings;
@ -46,8 +45,6 @@ namespace osu.Game.Rulesets
public abstract string Description { get; }
public abstract IEnumerable<KeyCounter> CreateGameplayKeys();
public virtual SettingsSubsection CreateSettings() => null;
/// <summary>

View File

@ -6,7 +6,7 @@ using SQLite.Net.Attributes;
namespace osu.Game.Rulesets
{
public class RulesetInfo
public class RulesetInfo : IEquatable<RulesetInfo>
{
[PrimaryKey, AutoIncrement]
public int? ID { get; set; }
@ -21,5 +21,7 @@ namespace osu.Game.Rulesets
public bool Available { get; set; }
public virtual Ruleset CreateInstance() => (Ruleset)Activator.CreateInstance(Type.GetType(InstantiationInfo), this);
public bool Equals(RulesetInfo other) => other != null && ID == other.ID && Available == other.Available && Name == other.Name && InstantiationInfo == other.InstantiationInfo;
}
}
}

View File

@ -20,7 +20,7 @@ namespace osu.Game.Rulesets.Timing
/// <summary>
/// The multiplier which this <see cref="MultiplierControlPoint"/> provides.
/// </summary>
public double Multiplier => 1000 / TimingPoint.BeatLength / DifficultyPoint.SpeedMultiplier;
public double Multiplier => 1000 / TimingPoint.BeatLength * DifficultyPoint.SpeedMultiplier;
/// <summary>
/// The <see cref="TimingControlPoint"/> that provides the timing information for this <see cref="MultiplierControlPoint"/>.
@ -62,4 +62,4 @@ namespace osu.Game.Rulesets.Timing
public int CompareTo(MultiplierControlPoint other) => StartTime.CompareTo(other?.StartTime);
}
}
}

View File

@ -27,6 +27,8 @@ namespace osu.Game.Rulesets.Timing
/// </summary>
internal Axes ScrollingAxes;
public override bool RemoveWhenNotAlive => false;
/// <summary>
/// The control point that defines the speed adjustments for this container. This is set by the <see cref="SpeedAdjustmentContainer"/>.
/// </summary>

View File

@ -34,6 +34,8 @@ namespace osu.Game.Rulesets.Timing
/// </summary>
public Axes ScrollingAxes { get; internal set; }
public override bool RemoveWhenNotAlive => false;
/// <summary>
/// The <see cref="MultiplierControlPoint"/> that defines the speed adjustments.
/// </summary>

View File

@ -48,7 +48,7 @@ namespace osu.Game.Rulesets.UI
/// <summary>
/// The key conversion input manager for this RulesetContainer.
/// </summary>
protected readonly PassThroughInputManager KeyConversionInputManager;
public readonly PassThroughInputManager KeyBindingInputManager;
/// <summary>
/// Whether we are currently providing the local user a gameplay cursor.
@ -76,8 +76,8 @@ namespace osu.Game.Rulesets.UI
internal RulesetContainer(Ruleset ruleset)
{
Ruleset = ruleset;
KeyConversionInputManager = CreateKeyBindingInputManager();
KeyConversionInputManager.RelativeSizeAxes = Axes.Both;
KeyBindingInputManager = CreateInputManager();
KeyBindingInputManager.RelativeSizeAxes = Axes.Both;
}
/// <summary>
@ -92,10 +92,10 @@ namespace osu.Game.Rulesets.UI
public abstract ScoreProcessor CreateScoreProcessor();
/// <summary>
/// Creates a key conversion input manager.
/// Creates a key conversion input manager. An exception will be thrown if a valid <see cref="RulesetInputManager{T}"/> is not returned.
/// </summary>
/// <returns>The input manager.</returns>
public virtual PassThroughInputManager CreateKeyBindingInputManager() => new PassThroughInputManager();
public abstract PassThroughInputManager CreateInputManager();
protected virtual FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new FramedReplayInputHandler(replay);
@ -253,7 +253,7 @@ namespace osu.Game.Rulesets.UI
InputManager.Add(content = new Container
{
RelativeSizeAxes = Axes.Both,
Children = new[] { KeyConversionInputManager }
Children = new[] { KeyBindingInputManager }
});
AddInternal(InputManager);
@ -262,7 +262,7 @@ namespace osu.Game.Rulesets.UI
[BackgroundDependencyLoader]
private void load()
{
KeyConversionInputManager.Add(Playfield = CreatePlayfield());
KeyBindingInputManager.Add(Playfield = CreatePlayfield());
loadObjects();

View File

@ -0,0 +1,44 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
using osu.Game.Screens.Play;
namespace osu.Game.Rulesets.UI
{
public abstract class RulesetInputManager<T> : DatabasedKeyBindingInputManager<T>, ICanAttachKeyCounter
where T : struct
{
protected RulesetInputManager(RulesetInfo ruleset, int variant, SimultaneousBindingMode unique) : base(ruleset, variant, unique)
{
}
public void Attach(KeyCounterCollection keyCounter)
{
var receptor = new ActionReceptor(keyCounter);
Add(receptor);
keyCounter.SetReceptor(receptor);
keyCounter.AddRange(DefaultKeyBindings.Select(b => b.GetAction<T>()).Distinct().Select(b => new KeyCounterAction<T>(b)));
}
public class ActionReceptor : KeyCounterCollection.Receptor, IKeyBindingHandler<T>
{
public ActionReceptor(KeyCounterCollection target)
: base(target)
{
}
public bool OnPressed(T action) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnPressed(action));
public bool OnReleased(T action) => Target.Children.OfType<KeyCounterAction<T>>().Any(c => c.OnReleased(action));
}
}
public interface ICanAttachKeyCounter
{
void Attach(KeyCounterCollection keyCounter);
}
}

View File

@ -7,6 +7,7 @@ using System.Linq;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Transforms;
using osu.Framework.Input;
using osu.Framework.MathUtils;
@ -150,11 +151,7 @@ namespace osu.Game.Rulesets.UI
/// </summary>
public readonly BindableBool Reversed = new BindableBool();
/// <summary>
/// Hit objects that are to be re-processed on the next update.
/// </summary>
private readonly List<DrawableHitObject> queuedHitObjects = new List<DrawableHitObject>();
private readonly List<SpeedAdjustmentContainer> speedAdjustments = new List<SpeedAdjustmentContainer>();
private readonly Container<SpeedAdjustmentContainer> speedAdjustments;
private readonly Axes scrollingAxes;
@ -165,6 +162,11 @@ namespace osu.Game.Rulesets.UI
public ScrollingHitObjectContainer(Axes scrollingAxes)
{
this.scrollingAxes = scrollingAxes;
AddInternal(speedAdjustments = new Container<SpeedAdjustmentContainer> { RelativeSizeAxes = Axes.Both });
// Default speed adjustment
AddSpeedAdjustment(new SpeedAdjustmentContainer(new MultiplierControlPoint(0)));
}
/// <summary>
@ -176,9 +178,22 @@ namespace osu.Game.Rulesets.UI
speedAdjustment.ScrollingAxes = scrollingAxes;
speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange);
speedAdjustment.Reversed.BindTo(Reversed);
speedAdjustments.Add(speedAdjustment);
AddInternal(speedAdjustment);
// We now need to re-sort the hit objects in the last speed adjustment prior to this one, to see if they need a new parent
var previousSpeedAdjustment = speedAdjustments.LastOrDefault(s => s.ControlPoint.StartTime < speedAdjustment.ControlPoint.StartTime);
if (previousSpeedAdjustment == null)
return;
foreach (DrawableHitObject h in previousSpeedAdjustment.Children)
{
var newSpeedAdjustment = adjustmentContainerFor(h);
if (newSpeedAdjustment == previousSpeedAdjustment)
continue;
previousSpeedAdjustment.Remove(h);
newSpeedAdjustment.Add(h);
}
}
public override IEnumerable<DrawableHitObject> Objects => speedAdjustments.SelectMany(s => s.Children);
@ -193,30 +208,14 @@ namespace osu.Game.Rulesets.UI
if (!(hitObject is IScrollingHitObject))
throw new InvalidOperationException($"Hit objects added to a {nameof(ScrollingHitObjectContainer)} must implement {nameof(IScrollingHitObject)}.");
queuedHitObjects.Add(hitObject);
var target = adjustmentContainerFor(hitObject);
if (target == null)
throw new InvalidOperationException($"A {nameof(SpeedAdjustmentContainer)} to container {hitObject} could not be found.");
target.Add(hitObject);
}
public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject)) || queuedHitObjects.Remove(hitObject);
protected override void Update()
{
base.Update();
// Todo: At the moment this is going to re-process every single Update, however this will only be a null-op
// when there are no SpeedAdjustmentContainers available. This should probably error or something, but it's okay for now.
for (int i = queuedHitObjects.Count - 1; i >= 0; i--)
{
var hitObject = queuedHitObjects[i];
var target = adjustmentContainerFor(hitObject);
if (target != null)
{
target.Add(hitObject);
queuedHitObjects.RemoveAt(i);
}
}
}
public override bool Remove(DrawableHitObject hitObject) => speedAdjustments.Any(s => s.Remove(hitObject));
/// <summary>
/// Finds the <see cref="SpeedAdjustmentContainer"/> which provides the speed adjustment active at the start time
@ -236,4 +235,4 @@ namespace osu.Game.Rulesets.UI
private SpeedAdjustmentContainer adjustmentContainerAt(double time) => speedAdjustments.FirstOrDefault(c => c.CanContain(time)) ?? speedAdjustments.LastOrDefault();
}
}
}
}

View File

@ -92,7 +92,7 @@ namespace osu.Game.Screens.Play
public virtual void BindRulesetContainer(RulesetContainer rulesetContainer)
{
rulesetContainer.InputManager.Add(KeyCounter.GetReceptor());
(rulesetContainer.KeyBindingInputManager as ICanAttachKeyCounter)?.Attach(KeyCounter);
replayLoaded = rulesetContainer.HasReplayLoaded;

View File

@ -0,0 +1,30 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Input.Bindings;
namespace osu.Game.Screens.Play
{
public class KeyCounterAction<T> : KeyCounter, IKeyBindingHandler<T>
where T : struct
{
public T Action { get; }
public KeyCounterAction(T action) : base($"B{(int)(object)action + 1}")
{
Action = action;
}
public bool OnPressed(T action)
{
if (action.Equals(Action)) IsLit = true;
return false;
}
public bool OnReleased(T action)
{
if (action.Equals(Action)) IsLit = false;
return false;
}
}
}

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -117,27 +118,36 @@ namespace osu.Game.Screens.Play
return receptor ?? (receptor = new Receptor(this));
}
public void SetReceptor(Receptor receptor)
{
if (this.receptor != null)
throw new InvalidOperationException("Cannot set a new receptor when one is already active");
this.receptor = receptor;
}
public class Receptor : Drawable
{
private readonly KeyCounterCollection target;
protected readonly KeyCounterCollection Target;
public Receptor(KeyCounterCollection target)
{
RelativeSizeAxes = Axes.Both;
this.target = target;
Depth = float.MinValue;
Target = target;
}
public override bool ReceiveMouseInputAt(Vector2 screenSpacePos) => true;
public override bool HandleInput => true;
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => target.Children.Any(c => c.TriggerOnKeyDown(state, args));
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyDown(state, args));
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => target.Children.Any(c => c.TriggerOnKeyUp(state, args));
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => Target.Children.Any(c => c.TriggerOnKeyUp(state, args));
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => target.Children.Any(c => c.TriggerOnMouseDown(state, args));
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args) => Target.Children.Any(c => c.TriggerOnMouseDown(state, args));
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => target.Children.Any(c => c.TriggerOnMouseUp(state, args));
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args) => Target.Children.Any(c => c.TriggerOnMouseUp(state, args));
}
}
}

View File

@ -76,8 +76,6 @@ namespace osu.Game.Screens.Play
sampleRestart = audio.Sample.Get(@"Gameplay/restart");
Ruleset rulesetInstance;
WorkingBeatmap working = Beatmap.Value;
Beatmap beatmap;
@ -89,7 +87,7 @@ namespace osu.Game.Screens.Play
throw new InvalidOperationException("Beatmap was not loaded");
ruleset = osu?.Ruleset.Value ?? beatmap.BeatmapInfo.Ruleset;
rulesetInstance = ruleset.CreateInstance();
var rulesetInstance = ruleset.CreateInstance();
try
{
@ -192,7 +190,6 @@ namespace osu.Game.Screens.Play
scoreProcessor = RulesetContainer.CreateScoreProcessor();
hudOverlay.KeyCounter.AddRange(rulesetInstance.CreateGameplayKeys());
hudOverlay.BindProcessor(scoreProcessor);
hudOverlay.BindRulesetContainer(RulesetContainer);

View File

@ -253,8 +253,6 @@ namespace osu.Game.Screens.Select
}
else
{
Ruleset.Value = beatmap.Ruleset;
if (beatmap.BeatmapSetInfoID == beatmapNoDebounce?.BeatmapSetInfoID)
sampleChangeDifficulty.Play();
else
@ -372,6 +370,9 @@ namespace osu.Game.Screens.Select
if (!track.IsRunning)
{
// Ensure the track is added to the TrackManager, since it is removed after the player finishes the map.
// Using AddItemToList rather than AddItem so that it doesn't attempt to register adjustment dependencies more than once.
Game.Audio.Track.AddItemToList(track);
if (preview) track.Seek(Beatmap.Value.Metadata.PreviewTime);
track.Start();
}

View File

@ -101,6 +101,7 @@
<Compile Include="Online\API\Requests\GetUsersRequest.cs" />
<Compile Include="Online\API\Requests\PostMessageRequest.cs" />
<Compile Include="Online\Chat\ErrorMessage.cs" />
<Compile Include="Online\Chat\LocalEchoMessage.cs" />
<Compile Include="Overlays\Chat\ChatTabControl.cs" />
<Compile Include="Overlays\KeyBinding\GlobalKeyBindingsSection.cs" />
<Compile Include="Overlays\KeyBinding\KeyBindingRow.cs" />
@ -128,6 +129,8 @@
<Compile Include="Overlays\Profile\Sections\RecentSection.cs" />
<Compile Include="Graphics\Containers\ConstrainedIconContainer.cs" />
<Compile Include="Rulesets\Mods\IApplicableToDifficulty.cs" />
<Compile Include="Rulesets\UI\RulesetInputManager.cs" />
<Compile Include="Screens\Play\KeyCounterAction.cs" />
<Compile Include="Users\UserCoverBackground.cs" />
<Compile Include="Overlays\UserProfileOverlay.cs" />
<Compile Include="Overlays\Profile\ProfileHeader.cs" />