1
0
mirror of https://github.com/ppy/osu.git synced 2025-02-15 14:23:02 +08:00

Merge branch 'master' into mask-settings-overlay

This commit is contained in:
Dean Herbert 2017-08-22 00:22:39 +09:00 committed by GitHub
commit 7693fc1382
60 changed files with 552 additions and 335 deletions

@ -1 +1 @@
Subproject commit 825505e788c4f093b269c61b485d38d50cd68096
Subproject commit f1527e5456cd228ddfb68cf6d56eb5d28dc360bf

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

@ -16,6 +16,8 @@ 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
@ -30,10 +32,11 @@ namespace osu.Desktop.Tests.Visual
protected override double TimePerAction => default_duration * 2;
private readonly Random rng = new Random(1337);
private readonly TaikoRulesetContainer rulesetContainer;
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));
@ -82,7 +85,7 @@ namespace osu.Desktop.Tests.Visual
RelativeSizeAxes = Axes.X,
Height = 768,
Clock = new FramedClock(rateAdjustClock),
Children = new[] { rulesetContainer = new TaikoRulesetContainer(null, beatmap, true) }
Children = new[] { rulesetContainer = new TaikoRulesetContainer(rulesets.GetRuleset(1).CreateInstance(), beatmap, true) }
});
}

View File

@ -3,11 +3,11 @@
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, 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;
@ -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

@ -6,6 +6,7 @@ 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
@ -97,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;
@ -118,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) && ((Parent as DrawableSlider)?.OsuActionInputManager?.PressedActions.Any(x => x == OsuAction.LeftButton || x == OsuAction.RightButton) ?? false);
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

@ -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

@ -3,11 +3,11 @@
using System.ComponentModel;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
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, 0, SimultaneousBindingMode.Unique)
{

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;
@ -29,8 +27,8 @@ namespace osu.Game.Rulesets.Osu
{
new KeyBinding(InputKey.Z, OsuAction.LeftButton),
new KeyBinding(InputKey.X, OsuAction.RightButton),
new KeyBinding(InputKey.LastKey + 1, OsuAction.LeftButton),
new KeyBinding(InputKey.LastKey + 2, 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

@ -58,10 +58,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
};
}
protected override TaikoJudgement CreateJudgement() => new TaikoJudgement();
protected override TaikoJudgement CreateJudgement() => null;
protected override void UpdateState(ArmedState state)
{
}
}
}
}

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

@ -56,6 +56,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected override TaikoPiece CreateMainPiece() => new ElongatedCirclePiece();
public override bool OnPressed(TaikoAction action) => false;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{

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
@ -59,7 +58,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
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.
@ -62,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.
@ -211,8 +210,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
}
}
protected override bool HandleKeyPress(Key key)
public override bool OnPressed(TaikoAction action)
{
if (Judgement.Result != HitResult.None)
return false;
@ -222,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> : DrawableScrollingHitObject<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;
@ -46,20 +39,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
protected virtual TaikoPiece CreateMainPiece() => new CirclePiece();
protected virtual bool HandleKeyPress(Key key) => false;
public abstract bool OnPressed(TaikoAction action);
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

@ -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

@ -45,6 +45,8 @@ namespace osu.Game.Rulesets.Taiko.UI
private readonly Container topLevelHitContainer;
private readonly Container barlineContainer;
private readonly Container overlayBackgroundContainer;
private readonly Container backgroundContainer;
@ -85,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,
@ -103,13 +105,21 @@ namespace osu.Game.Rulesets.Taiko.UI
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
FillMode = FillMode.Fit
},
content = 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",
@ -198,6 +208,10 @@ 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)
@ -239,4 +253,4 @@ namespace osu.Game.Rulesets.Taiko.UI
hitExplosionContainer.Children.FirstOrDefault(e => e.Judgement == judgedObject.Judgement)?.VisualiseSecondHit();
}
}
}
}

View File

@ -18,6 +18,7 @@ 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
{
@ -92,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)

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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -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

@ -151,10 +151,6 @@ 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 Container<SpeedAdjustmentContainer> speedAdjustments;
private readonly Axes scrollingAxes;
@ -168,6 +164,9 @@ namespace osu.Game.Rulesets.UI
this.scrollingAxes = scrollingAxes;
AddInternal(speedAdjustments = new Container<SpeedAdjustmentContainer> { RelativeSizeAxes = Axes.Both });
// Default speed adjustment
AddSpeedAdjustment(new SpeedAdjustmentContainer(new MultiplierControlPoint(0)));
}
/// <summary>
@ -180,6 +179,21 @@ namespace osu.Game.Rulesets.UI
speedAdjustment.VisibleTimeRange.BindTo(VisibleTimeRange);
speedAdjustment.Reversed.BindTo(Reversed);
speedAdjustments.Add(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);
@ -194,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)
continue;
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
@ -237,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

@ -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" />