1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-17 03:02:36 +08:00

Compare commits

..

301 Commits

164 changed files with 3255 additions and 1590 deletions
+1 -1
View File
@@ -14,7 +14,7 @@ namespace osu.Desktop.Tests.Visual
[Test]
public override void RunTest()
{
using (var host = new HeadlessGameHost())
using (var host = new HeadlessGameHost(realtime: false))
host.Run(new OsuTestCaseTestRunner(this));
}
+15 -15
View File
@@ -69,28 +69,28 @@ namespace osu.Desktop.Tests.Visual
private class MyContextMenuContainer : Container, IHasContextMenu
{
public ContextMenuItem[] ContextMenuItems => new ContextMenuItem[]
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuContextMenuItem(@"Some option"),
new OsuContextMenuItem(@"Highlighted option", MenuItemType.Highlighted),
new OsuContextMenuItem(@"Another option"),
new OsuContextMenuItem(@"Choose me please"),
new OsuContextMenuItem(@"And me too"),
new OsuContextMenuItem(@"Trying to fill"),
new OsuContextMenuItem(@"Destructive option", MenuItemType.Destructive),
new OsuMenuItem(@"Some option"),
new OsuMenuItem(@"Highlighted option", MenuItemType.Highlighted),
new OsuMenuItem(@"Another option"),
new OsuMenuItem(@"Choose me please"),
new OsuMenuItem(@"And me too"),
new OsuMenuItem(@"Trying to fill"),
new OsuMenuItem(@"Destructive option", MenuItemType.Destructive),
};
}
private class AnotherContextMenuContainer : Container, IHasContextMenu
{
public ContextMenuItem[] ContextMenuItems => new ContextMenuItem[]
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuContextMenuItem(@"Simple option"),
new OsuContextMenuItem(@"Simple very very long option"),
new OsuContextMenuItem(@"Change width", MenuItemType.Highlighted) { Action = () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint) },
new OsuContextMenuItem(@"Change height", MenuItemType.Highlighted) { Action = () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint) },
new OsuContextMenuItem(@"Change width back", MenuItemType.Destructive) { Action = () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint) },
new OsuContextMenuItem(@"Change height back", MenuItemType.Destructive) { Action = () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint) },
new OsuMenuItem(@"Simple option"),
new OsuMenuItem(@"Simple very very long option"),
new OsuMenuItem(@"Change width", MenuItemType.Highlighted, () => this.ResizeWidthTo(Width * 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change height", MenuItemType.Highlighted, () => this.ResizeHeightTo(Height * 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change width back", MenuItemType.Destructive, () => this.ResizeWidthTo(Width / 2, 100, Easing.OutQuint)),
new OsuMenuItem(@"Change height back", MenuItemType.Destructive, () => this.ResizeHeightTo(Height / 2, 100, Easing.OutQuint)),
};
}
}
@@ -3,6 +3,7 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using OpenTK;
@@ -40,8 +41,8 @@ namespace osu.Desktop.Tests.Visual
RelativeChildSize = new Vector2(1, 10000),
Children = new[]
{
new DrawableNote(new Note { StartTime = 5000 }) { AccentColour = Color4.Red },
new DrawableNote(new Note { StartTime = 6000 }) { AccentColour = Color4.Red }
new DrawableNote(new Note { StartTime = 5000 }, ManiaAction.Key1) { AccentColour = Color4.Red },
new DrawableNote(new Note { StartTime = 6000 }, ManiaAction.Key1) { AccentColour = Color4.Red }
}
}
}
@@ -66,7 +67,7 @@ namespace osu.Desktop.Tests.Visual
{
StartTime = 5000,
Duration = 1000
}) { AccentColour = Color4.Red }
}, ManiaAction.Key1) { AccentColour = Color4.Red }
}
}
}
@@ -1,111 +1,34 @@
// 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.Configuration;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Rulesets.Mania;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Timing;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Timing;
using OpenTK;
using OpenTK.Input;
using osu.Game.Rulesets;
namespace osu.Desktop.Tests.Visual
{
internal class TestCaseManiaPlayfield : OsuTestCase
{
private const double start_time = 500;
private const double duration = 500;
public override string Description => @"Mania playfield";
protected override double TimePerAction => 200;
private RulesetInfo maniaRuleset;
public TestCaseManiaPlayfield()
{
Action<int, SpecialColumnPosition> createPlayfield = (cols, pos) =>
{
Clear();
Add(new ManiaPlayfield(cols)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
SpecialColumnPosition = pos,
Scale = new Vector2(1, -1)
});
};
const double start_time = 500;
const double duration = 500;
Func<double, bool, SpeedAdjustmentContainer> createTimingChange = (time, gravity) => new ManiaSpeedAdjustmentContainer(new MultiplierControlPoint(time)
{
TimingPoint = { BeatLength = 1000 }
}, gravity ? ScrollingAlgorithm.Gravity : ScrollingAlgorithm.Basic);
Action<bool> createPlayfieldWithNotes = gravity =>
{
Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
ManiaPlayfield playField;
Add(playField = new ManiaPlayfield(4)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Scale = new Vector2(1, -1),
Clock = new FramedClock(rateAdjustClock)
});
if (!gravity)
playField.Columns.ForEach(c => c.Add(createTimingChange(0, false)));
for (double t = start_time; t <= start_time + duration; t += 100)
{
if (gravity)
playField.Columns.ElementAt(0).Add(createTimingChange(t, true));
playField.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 0
}, new Bindable<Key>(Key.D)));
if (gravity)
playField.Columns.ElementAt(3).Add(createTimingChange(t, true));
playField.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 3
}, new Bindable<Key>(Key.K)));
}
if (gravity)
playField.Columns.ElementAt(1).Add(createTimingChange(start_time, true));
playField.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 1
}, new Bindable<Key>(Key.F)));
if (gravity)
playField.Columns.ElementAt(2).Add(createTimingChange(start_time, true));
playField.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 2
}, new Bindable<Key>(Key.J)));
};
AddStep("1 column", () => createPlayfield(1, SpecialColumnPosition.Normal));
AddStep("4 columns", () => createPlayfield(4, SpecialColumnPosition.Normal));
AddStep("Left special style", () => createPlayfield(4, SpecialColumnPosition.Left));
@@ -114,29 +37,105 @@ namespace osu.Desktop.Tests.Visual
AddStep("8 columns", () => createPlayfield(8, SpecialColumnPosition.Normal));
AddStep("Left special style", () => createPlayfield(8, SpecialColumnPosition.Left));
AddStep("Right special style", () => createPlayfield(8, SpecialColumnPosition.Right));
AddStep("Reversed", () => createPlayfield(4, SpecialColumnPosition.Normal, true));
AddStep("Notes with input", () => createPlayfieldWithNotes(false));
AddWaitStep((int)Math.Ceiling((start_time + duration) / TimePerAction));
AddStep("Notes with input (reversed)", () => createPlayfieldWithNotes(false, true));
AddStep("Notes with gravity", () => createPlayfieldWithNotes(true));
AddWaitStep((int)Math.Ceiling((start_time + duration) / TimePerAction));
AddStep("Notes with gravity (reversed)", () => createPlayfieldWithNotes(true, true));
}
private void triggerKeyDown(Column column)
[BackgroundDependencyLoader]
private void load(RulesetStore rulesets)
{
column.TriggerOnKeyDown(new InputState(), new KeyDownEventArgs
{
Key = column.Key,
Repeat = false
});
maniaRuleset = rulesets.GetRuleset(3);
}
private void triggerKeyUp(Column column)
private SpeedAdjustmentContainer createTimingChange(double time, bool gravity) => new ManiaSpeedAdjustmentContainer(new MultiplierControlPoint(time)
{
column.TriggerOnKeyUp(new InputState(), new KeyUpEventArgs
TimingPoint = { BeatLength = 1000 }
}, gravity ? ScrollingAlgorithm.Gravity : ScrollingAlgorithm.Basic);
private void createPlayfield(int cols, SpecialColumnPosition specialPos, bool inverted = false)
{
Clear();
var inputManager = new ManiaInputManager(maniaRuleset, cols) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
inputManager.Add(playfield = new ManiaPlayfield(cols)
{
Key = column.Key
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
SpecialColumnPosition = specialPos
});
playfield.Inverted.Value = inverted;
}
private void createPlayfieldWithNotes(bool gravity, bool inverted = false)
{
Clear();
var rateAdjustClock = new StopwatchClock(true) { Rate = 1 };
var inputManager = new ManiaInputManager(maniaRuleset, 4) { RelativeSizeAxes = Axes.Both };
Add(inputManager);
ManiaPlayfield playfield;
inputManager.Add(playfield = new ManiaPlayfield(4)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Clock = new FramedClock(rateAdjustClock)
});
playfield.Inverted.Value = inverted;
if (!gravity)
playfield.Columns.ForEach(c => c.Add(createTimingChange(0, false)));
for (double t = start_time; t <= start_time + duration; t += 100)
{
if (gravity)
playfield.Columns.ElementAt(0).Add(createTimingChange(t, true));
playfield.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 0
}, ManiaAction.Key1));
if (gravity)
playfield.Columns.ElementAt(3).Add(createTimingChange(t, true));
playfield.Add(new DrawableNote(new Note
{
StartTime = t,
Column = 3
}, ManiaAction.Key4));
}
if (gravity)
playfield.Columns.ElementAt(1).Add(createTimingChange(start_time, true));
playfield.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 1
}, ManiaAction.Key2));
if (gravity)
playfield.Columns.ElementAt(2).Add(createTimingChange(start_time, true));
playfield.Add(new DrawableHoldNote(new HoldNote
{
StartTime = start_time,
Duration = duration,
Column = 2
}, ManiaAction.Key3));
}
}
}
@@ -3,18 +3,23 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using OpenTK;
using osu.Desktop.Tests.Beatmaps;
using osu.Framework.Extensions.IEnumerableExtensions;
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.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI;
namespace osu.Desktop.Tests.Visual
@@ -22,6 +27,7 @@ namespace osu.Desktop.Tests.Visual
/// <summary>
/// The most minimal implementation of a playfield with scrolling hit objects.
/// </summary>
[TestFixture]
public class TestCaseScrollingPlayfield : OsuTestCase
{
public TestCaseScrollingPlayfield()
@@ -58,11 +64,71 @@ namespace osu.Desktop.Tests.Visual
AddStep("Reverse direction", () =>
{
horizontalRulesetContainer.Playfield.Reversed.Toggle();
verticalRulesetContainer.Playfield.Reversed.Toggle();
horizontalRulesetContainer.Playfield.Reverse();
verticalRulesetContainer.Playfield.Reverse();
});
}
[Test]
public void TestSpeedAdjustmentOrdering()
{
var hitObjectContainer = new ScrollingPlayfield<TestHitObject, TestJudgement>.ScrollingHitObjectContainer(Axes.X);
var speedAdjustments = new[]
{
new SpeedAdjustmentContainer(new MultiplierControlPoint()),
new SpeedAdjustmentContainer(new MultiplierControlPoint(1000)
{
TimingPoint = new TimingControlPoint { BeatLength = 500 }
}),
new SpeedAdjustmentContainer(new MultiplierControlPoint(2000)
{
TimingPoint = new TimingControlPoint { BeatLength = 1000 },
DifficultyPoint = new DifficultyControlPoint { SpeedMultiplier = 2}
}),
new SpeedAdjustmentContainer(new MultiplierControlPoint(3000)
{
TimingPoint = new TimingControlPoint { BeatLength = 1000 },
DifficultyPoint = new DifficultyControlPoint { SpeedMultiplier = 1}
}),
};
var hitObjects = new[]
{
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = -1000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject()),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 1000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 2000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 3000 }),
new DrawableTestHitObject(Axes.X, new TestHitObject { StartTime = 4000 }),
};
hitObjects.ForEach(h => hitObjectContainer.Add(h));
speedAdjustments.ForEach(hitObjectContainer.AddSpeedAdjustment);
// The 0th index in hitObjectContainer.SpeedAdjustments is the "default" control point
// Check multiplier of the default speed adjustment
Assert.AreEqual(1, hitObjectContainer.SpeedAdjustments[0].ControlPoint.Multiplier);
Assert.AreEqual(1, speedAdjustments[0].ControlPoint.Multiplier);
Assert.AreEqual(2, speedAdjustments[1].ControlPoint.Multiplier);
Assert.AreEqual(2, speedAdjustments[2].ControlPoint.Multiplier);
Assert.AreEqual(1, speedAdjustments[3].ControlPoint.Multiplier);
// Check insertion of hit objects
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[4].Contains(hitObjects[0]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[2].Contains(hitObjects[2]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[1].Contains(hitObjects[3]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[4]));
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[0].Contains(hitObjects[5]));
hitObjectContainer.RemoveSpeedAdjustment(hitObjectContainer.SpeedAdjustments[3]);
// The hit object contained in this speed adjustment should be resorted into the one occuring before it
Assert.IsTrue(hitObjectContainer.SpeedAdjustments[3].Contains(hitObjects[1]));
}
private class TestRulesetContainer : ScrollingRulesetContainer<TestPlayfield, TestHitObject, TestJudgement>
{
private readonly Axes scrollingAxes;
@@ -77,6 +143,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);
@@ -142,6 +210,8 @@ namespace osu.Desktop.Tests.Visual
content = new Container { RelativeSizeAxes = Axes.Both }
};
}
public void Reverse() => Reversed.Toggle();
}
@@ -155,4 +225,4 @@ namespace osu.Desktop.Tests.Visual
public override string MaxResultString { get { throw new NotImplementedException(); } }
}
}
}
}
@@ -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) }
});
}
+7 -8
View File
@@ -20,16 +20,11 @@ namespace osu.Desktop
{
internal class OsuGameDesktop : OsuGame
{
private readonly VersionManager versionManager;
private VersionManager versionManager;
public OsuGameDesktop(string[] args = null)
: base(args)
{
versionManager = new VersionManager
{
Depth = int.MinValue,
State = Visibility.Hidden
};
}
public override Storage GetStorageForStableInstall()
@@ -88,11 +83,15 @@ namespace osu.Desktop
{
base.LoadComplete();
LoadComponentAsync(versionManager, Add);
LoadComponentAsync(versionManager = new VersionManager { Depth = int.MinValue });
ScreenChanged += s =>
{
if (!versionManager.IsPresent && s is Intro)
if (s is Intro && s.ChildScreen == null)
{
Add(versionManager);
versionManager.State = Visibility.Visible;
}
};
}
+44 -1
View File
@@ -2,6 +2,7 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Diagnostics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
@@ -19,6 +20,7 @@ using OpenTK.Graphics;
using System.Net.Http;
using osu.Framework.Logging;
using osu.Game;
using osu.Game.Configuration;
namespace osu.Desktop.Overlays
{
@@ -26,17 +28,22 @@ namespace osu.Desktop.Overlays
{
private UpdateManager updateManager;
private NotificationOverlay notificationOverlay;
private OsuConfigManager config;
private OsuGameBase game;
public override bool HandleInput => false;
[BackgroundDependencyLoader]
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game)
private void load(NotificationOverlay notification, OsuColour colours, TextureStore textures, OsuGameBase game, OsuConfigManager config)
{
notificationOverlay = notification;
this.config = config;
this.game = game;
AutoSizeAxes = Axes.Both;
Anchor = Anchor.BottomCentre;
Origin = Anchor.BottomCentre;
Alpha = 0;
Children = new Drawable[]
@@ -91,6 +98,42 @@ namespace osu.Desktop.Overlays
checkForUpdateAsync();
}
protected override void LoadComplete()
{
base.LoadComplete();
var version = game.Version;
var lastVersion = config.Get<string>(OsuSetting.Version);
if (game.IsDeployedBuild && version != lastVersion)
{
config.Set(OsuSetting.Version, version);
// only show a notification if we've previously saved a version to the config file (ie. not the first run).
if (!string.IsNullOrEmpty(lastVersion))
Scheduler.AddDelayed(() => notificationOverlay.Post(new UpdateCompleteNotification(version)), 5000);
}
}
private class UpdateCompleteNotification : SimpleNotification
{
public UpdateCompleteNotification(string version)
{
Text = $"You are now running osu!lazer {version}.\nClick to see what's new!";
Icon = FontAwesome.fa_check_square;
Activated = delegate
{
Process.Start($"https://github.com/ppy/osu/releases/tag/v{version}");
return true;
};
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
IconBackgound.Colour = colours.BlueDark;
}
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
+2 -2
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)
-9
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();
@@ -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)
{
@@ -28,12 +28,20 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
private Pattern lastPattern = new Pattern();
private FastRandom random;
private Beatmap beatmap;
private bool isForCurrentRuleset;
protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset)
private readonly int availableColumns;
private readonly bool isForCurrentRuleset;
public ManiaBeatmapConverter(bool isForCurrentRuleset, int availableColumns)
{
this.isForCurrentRuleset = isForCurrentRuleset;
if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
this.isForCurrentRuleset = isForCurrentRuleset;
this.availableColumns = availableColumns;
}
protected override Beatmap<ManiaHitObject> ConvertBeatmap(Beatmap original)
{
beatmap = original;
BeatmapDifficulty difficulty = original.BeatmapInfo.Difficulty;
@@ -41,7 +49,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
int seed = (int)Math.Round(difficulty.DrainRate + difficulty.CircleSize) * 20 + (int)(difficulty.OverallDifficulty * 41.2) + (int)Math.Round(difficulty.ApproachRate);
random = new FastRandom(seed);
return base.ConvertBeatmap(original, isForCurrentRuleset);
return base.ConvertBeatmap(original);
}
protected override IEnumerable<ManiaHitObject> ConvertHitObject(HitObject original, Beatmap beatmap)
@@ -89,7 +97,7 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// <returns>The hit objects generated.</returns>
private IEnumerable<ManiaHitObject> generateSpecific(HitObject original)
{
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, lastPattern);
var generator = new SpecificBeatmapPatternGenerator(random, original, beatmap, availableColumns, lastPattern);
Pattern newPattern = generator.Generate();
lastPattern = newPattern;
@@ -113,14 +121,14 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
Patterns.PatternGenerator conversion = null;
if (distanceData != null)
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, lastPattern);
conversion = new DistanceObjectPatternGenerator(random, original, beatmap, availableColumns, lastPattern);
else if (endTimeData != null)
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap);
conversion = new EndTimeObjectPatternGenerator(random, original, beatmap, availableColumns);
else if (positionData != null)
{
computeDensity(original.StartTime);
conversion = new HitObjectPatternGenerator(random, original, beatmap, lastPattern, lastTime, lastPosition, density, lastStair);
conversion = new HitObjectPatternGenerator(random, original, beatmap, availableColumns, lastPattern, lastTime, lastPosition, density, lastStair);
recordNote(original.StartTime, positionData.Position);
}
@@ -142,8 +150,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps
/// </summary>
private class SpecificBeatmapPatternGenerator : Patterns.Legacy.PatternGenerator
{
public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern)
public SpecificBeatmapPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(random, hitObject, beatmap, availableColumns, previousPattern)
{
}
@@ -29,8 +29,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private PatternType convertType;
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
: base(random, hitObject, beatmap, previousPattern)
public DistanceObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(random, hitObject, beatmap, availableColumns, previousPattern)
{
convertType = PatternType.None;
if (Beatmap.ControlPointInfo.EffectPointAt(hitObject.StartTime).KiaiMode)
@@ -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;
@@ -15,8 +15,8 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
{
private readonly double endTime;
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap)
: base(random, hitObject, beatmap, new Pattern())
public EndTimeObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns)
: base(random, hitObject, beatmap, availableColumns, new Pattern())
{
var endtimeData = HitObject as IHasEndTime;
@@ -20,9 +20,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
private readonly PatternType convertType;
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair)
: base(random, hitObject, beatmap, previousPattern)
public HitObjectPatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern, double previousTime, Vector2 previousPosition, double density, PatternType lastStair)
: base(random, hitObject, beatmap, availableColumns, previousPattern)
{
if (previousTime > hitObject.StartTime) throw new ArgumentOutOfRangeException(nameof(previousTime));
if (density < 0) throw new ArgumentOutOfRangeException(nameof(density));
StairType = lastStair;
TimingControlPoint timingPoint = beatmap.ControlPointInfo.TimingPointAt(hitObject.StartTime);
@@ -25,11 +25,15 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// </summary>
protected readonly FastRandom Random;
protected PatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
: base(hitObject, beatmap, previousPattern)
protected PatternGenerator(FastRandom random, HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
: base(hitObject, beatmap, availableColumns, previousPattern)
{
Random = random;
if (random == null) throw new ArgumentNullException(nameof(random));
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
Random = random;
RandomStart = AvailableColumns == 8 ? 1 : 0;
}
@@ -62,6 +66,12 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns.Legacy
/// <returns>The amount of notes to be generated.</returns>
protected int GetRandomNoteCount(double p2, double p3, double p4 = 0, double p5 = 0, double p6 = 0)
{
if (p2 < 0 || p2 > 1) throw new ArgumentOutOfRangeException(nameof(p2));
if (p3 < 0 || p3 > 1) throw new ArgumentOutOfRangeException(nameof(p3));
if (p4 < 0 || p4 > 1) throw new ArgumentOutOfRangeException(nameof(p4));
if (p5 < 0 || p5 > 1) throw new ArgumentOutOfRangeException(nameof(p5));
if (p6 < 0 || p6 > 1) throw new ArgumentOutOfRangeException(nameof(p6));
double val = Random.NextDouble();
if (val >= 1 - p6)
return 6;
@@ -32,13 +32,17 @@ namespace osu.Game.Rulesets.Mania.Beatmaps.Patterns
/// </summary>
protected readonly Beatmap Beatmap;
protected PatternGenerator(HitObject hitObject, Beatmap beatmap, Pattern previousPattern)
protected PatternGenerator(HitObject hitObject, Beatmap beatmap, int availableColumns, Pattern previousPattern)
{
PreviousPattern = previousPattern;
if (hitObject == null) throw new ArgumentNullException(nameof(hitObject));
if (beatmap == null) throw new ArgumentNullException(nameof(beatmap));
if (availableColumns <= 0) throw new ArgumentOutOfRangeException(nameof(availableColumns));
if (previousPattern == null) throw new ArgumentNullException(nameof(previousPattern));
HitObject = hitObject;
Beatmap = beatmap;
AvailableColumns = (int)Math.Round(beatmap.BeatmapInfo.Difficulty.CircleSize);
AvailableColumns = availableColumns;
PreviousPattern = previousPattern;
}
/// <summary>
@@ -6,6 +6,7 @@ using osu.Game.Rulesets.Beatmaps;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Objects;
using System.Collections.Generic;
using System;
namespace osu.Game.Rulesets.Mania
{
@@ -21,6 +22,6 @@ namespace osu.Game.Rulesets.Mania
return 0;
}
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter();
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter(true, (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize)));
}
}
}
@@ -0,0 +1,41 @@
// 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.Mania
{
public class ManiaInputManager : RulesetInputManager<ManiaAction>
{
public ManiaInputManager(RulesetInfo ruleset, int variant)
: base(ruleset, variant, SimultaneousBindingMode.Unique)
{
}
}
public enum ManiaAction
{
[Description("Special")]
Special,
[Description("Key 1")]
Key1 = 10,
[Description("Key 2")]
Key2,
[Description("Key 3")]
Key3,
[Description("Key 4")]
Key4,
[Description("Key 5")]
Key5,
[Description("Key 6")]
Key6,
[Description("Key 7")]
Key7,
[Description("Key 8")]
Key8,
[Description("Key 9")]
Key9
}
}
+39 -3
View File
@@ -6,9 +6,9 @@ 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.Framework.Input.Bindings;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Scoring;
using osu.Game.Rulesets.Scoring;
@@ -111,8 +111,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();
@@ -123,5 +121,43 @@ namespace osu.Game.Rulesets.Mania
: base(rulesetInfo)
{
}
public override IEnumerable<int> AvailableVariants => new[] { 1, 2, 3, 4, 5, 6, 7, 8, 9 };
public override IEnumerable<KeyBinding> GetDefaultKeyBindings(int variant = 0)
{
var leftKeys = new[]
{
InputKey.A,
InputKey.S,
InputKey.D,
InputKey.F
};
var rightKeys = new[]
{
InputKey.J,
InputKey.K,
InputKey.L,
InputKey.Semicolon
};
ManiaAction currentKey = ManiaAction.Key1;
var bindings = new List<KeyBinding>();
for (int i = leftKeys.Length - variant / 2; i < leftKeys.Length; i++)
bindings.Add(new KeyBinding(leftKeys[i], currentKey++));
for (int i = 0; i < variant / 2; i++)
bindings.Add(new KeyBinding(rightKeys[i], currentKey++));
if (variant % 2 == 1)
bindings.Add(new KeyBinding(InputKey.Space, ManiaAction.Special));
return bindings;
}
public override string GetVariantName(int variant) => $"{variant}K";
}
}
@@ -5,20 +5,18 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics;
using osu.Framework.Configuration;
using OpenTK.Input;
using osu.Framework.Input;
using OpenTK;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input.Bindings;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
/// <summary>
/// Visualises a <see cref="HoldNote"/> hit object.
/// </summary>
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>
public class DrawableHoldNote : DrawableManiaHitObject<HoldNote>, IKeyBindingHandler<ManiaAction>
{
private readonly DrawableNote head;
private readonly DrawableNote tail;
@@ -36,8 +34,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// </summary>
private bool hasBroken;
public DrawableHoldNote(HoldNote hitObject, Bindable<Key> key = null)
: base(hitObject, key)
public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
: base(hitObject, action)
{
RelativeSizeAxes = Axes.Both;
Height = (float)HitObject.Duration;
@@ -58,12 +56,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
RelativeChildOffset = new Vector2(0, (float)HitObject.StartTime),
RelativeChildSize = new Vector2(1, (float)HitObject.Duration)
},
head = new DrawableHeadNote(this, key)
head = new DrawableHeadNote(this, action)
{
Anchor = Anchor.TopCentre,
Origin = Anchor.TopCentre
},
tail = new DrawableTailNote(this, key)
tail = new DrawableTailNote(this, action)
{
Anchor = Anchor.BottomCentre,
Origin = Anchor.TopCentre
@@ -106,16 +104,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
public bool OnPressed(ManiaAction action)
{
// Make sure the keypress happened within the body of the hold note
// Make sure the action happened within the body of the hold note
if (Time.Current < HitObject.StartTime || Time.Current > HitObject.EndTime)
return false;
if (args.Key != Key)
return false;
if (args.Repeat)
if (action != Action)
return false;
// The user has pressed during the body of the hold note, after the head note and its hit windows have passed
@@ -126,13 +121,13 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
return true;
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
public bool OnReleased(ManiaAction action)
{
// Make sure that the user started holding the key during the hold note
if (!holdStartTime.HasValue)
return false;
if (args.Key != Key)
if (action != Action)
return false;
holdStartTime = null;
@@ -151,8 +146,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly DrawableHoldNote holdNote;
public DrawableHeadNote(DrawableHoldNote holdNote, Bindable<Key> key = null)
: base(holdNote.HitObject.Head, key)
public DrawableHeadNote(DrawableHoldNote holdNote, ManiaAction action)
: base(holdNote.HitObject.Head, action)
{
this.holdNote = holdNote;
@@ -160,9 +155,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Y = 0;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
public override bool OnPressed(ManiaAction action)
{
if (!base.OnKeyDown(state, args))
if (!base.OnPressed(action))
return false;
// We only want to trigger a holding state from the head if the head has received a judgement
@@ -188,8 +183,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
{
private readonly DrawableHoldNote holdNote;
public DrawableTailNote(DrawableHoldNote holdNote, Bindable<Key> key = null)
: base(holdNote.HitObject.Tail, key)
public DrawableTailNote(DrawableHoldNote holdNote, ManiaAction action)
: base(holdNote.HitObject.Tail, action)
{
this.holdNote = holdNote;
@@ -210,7 +205,9 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
tailJudgement.HasBroken = holdNote.hasBroken;
}
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args)
public override bool OnPressed(ManiaAction action) => false; // Tail doesn't handle key down
public override bool OnReleased(ManiaAction action)
{
// Make sure that the user started holding the key during the hold note
if (!holdNote.holdStartTime.HasValue)
@@ -219,7 +216,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (Judgement.Result != HitResult.None)
return false;
if (args.Key != Key)
if (action != Action)
return false;
UpdateJudgement(true);
@@ -227,12 +224,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
// Handled by the hold note, which will set holding = false
return false;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
// Tail doesn't handle key down
return false;
}
}
}
}
@@ -2,8 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Objects.Drawables;
@@ -15,17 +13,17 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// <summary>
/// The key that will trigger input for this hit object.
/// </summary>
protected Bindable<Key> Key { get; private set; } = new Bindable<Key>();
protected ManiaAction Action { get; }
public new TObject HitObject;
protected DrawableManiaHitObject(TObject hitObject, Bindable<Key> key = null)
protected DrawableManiaHitObject(TObject hitObject, ManiaAction? action = null)
: base(hitObject)
{
HitObject = hitObject;
if (key != null)
Key.BindTo(key);
if (action != null)
Action = action.Value;
}
public override Color4 AccentColour
@@ -3,10 +3,8 @@
using System;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Objects.Drawables;
@@ -16,12 +14,12 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
/// <summary>
/// Visualises a <see cref="Note"/> hit object.
/// </summary>
public class DrawableNote : DrawableManiaHitObject<Note>
public class DrawableNote : DrawableManiaHitObject<Note>, IKeyBindingHandler<ManiaAction>
{
private readonly NotePiece headPiece;
public DrawableNote(Note hitObject, Bindable<Key> key = null)
: base(hitObject, key)
public DrawableNote(Note hitObject, ManiaAction action)
: base(hitObject, action)
{
RelativeSizeAxes = Axes.X;
Height = 100;
@@ -81,18 +79,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
public virtual bool OnPressed(ManiaAction action)
{
if (Judgement.Result != HitResult.None)
return false;
if (args.Key != Key)
return false;
if (args.Repeat)
if (action != Action)
return false;
return UpdateJudgement(true);
}
public virtual bool OnReleased(ManiaAction action) => false;
}
}
+13 -21
View File
@@ -3,17 +3,15 @@
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Colour;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using System;
using osu.Framework.Configuration;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Judgements;
@@ -32,10 +30,7 @@ namespace osu.Game.Rulesets.Mania.UI
private const float column_width = 45;
private const float special_column_width = 70;
/// <summary>
/// The key that will trigger input actions for this column and hit objects contained inside it.
/// </summary>
public Bindable<Key> Key = new Bindable<Key>();
public ManiaAction Action;
private readonly Box background;
private readonly Container hitTargetBar;
@@ -101,8 +96,8 @@ namespace osu.Game.Rulesets.Mania.UI
// For column lighting, we need to capture input events before the notes
new InputTarget
{
KeyDown = onKeyDown,
KeyUp = onKeyUp
Pressed = onPressed,
Released = onReleased
}
}
},
@@ -199,12 +194,9 @@ namespace osu.Game.Rulesets.Mania.UI
HitObjects.Add(hitObject);
}
private bool onKeyDown(InputState state, KeyDownEventArgs args)
private bool onPressed(ManiaAction action)
{
if (args.Repeat)
return false;
if (args.Key == Key)
if (action == Action)
{
background.FadeTo(background.Alpha + 0.2f, 50, Easing.OutQuint);
keyIcon.ScaleTo(1.4f, 50, Easing.OutQuint);
@@ -213,9 +205,9 @@ namespace osu.Game.Rulesets.Mania.UI
return false;
}
private bool onKeyUp(InputState state, KeyUpEventArgs args)
private bool onReleased(ManiaAction action)
{
if (args.Key == Key)
if (action == Action)
{
background.FadeTo(0.2f, 800, Easing.OutQuart);
keyIcon.ScaleTo(1f, 400, Easing.OutQuart);
@@ -227,10 +219,10 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary>
/// This is a simple container which delegates various input events that have to be captured before the notes.
/// </summary>
private class InputTarget : Container
private class InputTarget : Container, IKeyBindingHandler<ManiaAction>
{
public Func<InputState, KeyDownEventArgs, bool> KeyDown;
public Func<InputState, KeyUpEventArgs, bool> KeyUp;
public Func<ManiaAction, bool> Pressed;
public Func<ManiaAction, bool> Released;
public InputTarget()
{
@@ -239,8 +231,8 @@ namespace osu.Game.Rulesets.Mania.UI
Alpha = 0;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args) => KeyDown?.Invoke(state, args) ?? false;
protected override bool OnKeyUp(InputState state, KeyUpEventArgs args) => KeyUp?.Invoke(state, args) ?? false;
public bool OnPressed(ManiaAction action) => Pressed?.Invoke(action) ?? false;
public bool OnReleased(ManiaAction action) => Released?.Invoke(action) ?? false;
}
}
}
+21 -20
View File
@@ -14,6 +14,7 @@ using osu.Framework.Allocation;
using OpenTK.Input;
using System.Linq;
using System.Collections.Generic;
using osu.Framework.Configuration;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Framework.Graphics.Shapes;
@@ -45,6 +46,11 @@ namespace osu.Game.Rulesets.Mania.UI
}
}
/// <summary>
/// Whether this playfield should be inverted. This flips everything inside the playfield.
/// </summary>
public readonly Bindable<bool> Inverted = new Bindable<bool>(true);
private readonly FlowContainer<Column> columns;
public IEnumerable<Column> Columns => columns.Children;
@@ -64,6 +70,8 @@ namespace osu.Game.Rulesets.Mania.UI
if (columnCount <= 0)
throw new ArgumentException("Can't have zero or fewer columns.");
Inverted.Value = true;
InternalChildren = new Drawable[]
{
new Container
@@ -122,14 +130,26 @@ namespace osu.Game.Rulesets.Mania.UI
}
};
var currentAction = ManiaAction.Key1;
for (int i = 0; i < columnCount; i++)
{
var c = new Column();
c.VisibleTimeRange.BindTo(VisibleTimeRange);
c.IsSpecial = isSpecialColumn(i);
c.Action = c.IsSpecial ? ManiaAction.Special : currentAction++;
columns.Add(c);
AddNested(c);
}
Inverted.ValueChanged += invertedChanged;
Inverted.TriggerChange();
}
private void invertedChanged(bool newValue)
{
Scale = new Vector2(1, newValue ? -1 : 1);
}
[BackgroundDependencyLoader]
@@ -144,15 +164,11 @@ namespace osu.Game.Rulesets.Mania.UI
specialColumnColour = colours.BlueDark;
// Set the special column + colour + key
for (int i = 0; i < columnCount; i++)
foreach (var column in Columns)
{
Column column = Columns.ElementAt(i);
column.IsSpecial = isSpecialColumn(i);
if (!column.IsSpecial)
continue;
column.Key.Value = Key.Space;
column.AccentColour = specialColumnColour;
}
@@ -166,21 +182,6 @@ namespace osu.Game.Rulesets.Mania.UI
nonSpecialColumns[i].AccentColour = colour;
nonSpecialColumns[nonSpecialColumns.Count - 1 - i].AccentColour = colour;
}
// We'll set the keys for non-special columns in another separate loop because it's not mirrored like the above colours
// Todo: This needs to go when we get to bindings and use Button1, ..., ButtonN instead
for (int i = 0; i < nonSpecialColumns.Count; i++)
{
Column column = nonSpecialColumns[i];
int keyOffset = default_keys.Length / 2 - nonSpecialColumns.Count / 2 + i;
if (keyOffset >= 0 && keyOffset < default_keys.Length)
column.Key.Value = default_keys[keyOffset];
else
// There is no default key defined for this column. Let's set this to Unknown for now
// however note that this will be gone after bindings are in place
column.Key.Value = Key.Unknown;
}
}
/// <summary>
@@ -5,11 +5,10 @@ using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Input;
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;
@@ -32,9 +31,10 @@ namespace osu.Game.Rulesets.Mania.UI
public class ManiaRulesetContainer : ScrollingRulesetContainer<ManiaPlayfield, ManiaHitObject, ManiaJudgement>
{
/// <summary>
/// Preferred column count. This will only have an effect during the initialization of the play field.
/// The number of columns which the <see cref="ManiaPlayfield"/> should display, and which
/// the beatmap converter will attempt to convert beatmaps to use.
/// </summary>
public int PreferredColumns;
private int availableColumns;
public IEnumerable<DrawableBarLine> BarLines;
@@ -75,36 +75,47 @@ namespace osu.Game.Rulesets.Mania.UI
BarLines.ForEach(Playfield.Add);
}
protected override void ApplyBeatmap()
{
base.ApplyBeatmap();
PreferredColumns = (int)Math.Max(1, Math.Round(Beatmap.BeatmapInfo.Difficulty.CircleSize));
}
protected sealed override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(PreferredColumns)
protected sealed override Playfield<ManiaHitObject, ManiaJudgement> CreatePlayfield() => new ManiaPlayfield(availableColumns)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
// Invert by default for now (should be moved to config/skin later)
Scale = new Vector2(1, -1)
};
public override ScoreProcessor CreateScoreProcessor() => new ManiaScoreProcessor(this);
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter() => new ManiaBeatmapConverter();
public override PassThroughInputManager CreateInputManager() => new ManiaInputManager(Ruleset.RulesetInfo, availableColumns);
protected override BeatmapConverter<ManiaHitObject> CreateBeatmapConverter()
{
if (IsForCurrentRuleset)
availableColumns = (int)Math.Max(1, Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize));
else
{
float percentSliderOrSpinner = (float)WorkingBeatmap.Beatmap.HitObjects.Count(h => h is IHasEndTime) / WorkingBeatmap.Beatmap.HitObjects.Count;
if (percentSliderOrSpinner < 0.2)
availableColumns = 7;
else if (percentSliderOrSpinner < 0.3 || Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.CircleSize) >= 5)
availableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 5 ? 7 : 6;
else if (percentSliderOrSpinner > 0.6)
availableColumns = Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) > 4 ? 5 : 4;
else
availableColumns = Math.Max(4, Math.Min((int)Math.Round(WorkingBeatmap.BeatmapInfo.Difficulty.OverallDifficulty) + 1, 7));
}
return new ManiaBeatmapConverter(IsForCurrentRuleset, availableColumns);
}
protected override DrawableHitObject<ManiaHitObject, ManiaJudgement> GetVisualRepresentation(ManiaHitObject h)
{
Bindable<Key> key = Playfield.Columns.ElementAt(h.Column).Key;
ManiaAction action = Playfield.Columns.ElementAt(h.Column).Action;
var holdNote = h as HoldNote;
if (holdNote != null)
return new DrawableHoldNote(holdNote, key);
return new DrawableHoldNote(holdNote, action);
var note = h as Note;
if (note != null)
return new DrawableNote(note, key);
return new DrawableNote(note, action);
return null;
}
@@ -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" />
+19 -1
View File
@@ -7,8 +7,13 @@ using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Osu.Objects;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Game.Rulesets.Osu.UI;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Mods
{
@@ -28,10 +33,23 @@ namespace osu.Game.Rulesets.Osu.Mods
public override double ScoreMultiplier => 1.06;
}
public class OsuModHardRock : ModHardRock
public class OsuModHardRock : ModHardRock, IApplicableMod<OsuHitObject>
{
public override double ScoreMultiplier => 1.06;
public override bool Ranked => true;
public void ApplyToRulesetContainer(RulesetContainer<OsuHitObject> rulesetContainer)
{
rulesetContainer.Objects.OfType<OsuHitObject>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Y));
rulesetContainer.Objects.OfType<Slider>().ForEach(s =>
{
var newControlPoints = new List<Vector2>();
s.ControlPoints.ForEach(c => newControlPoints.Add(new Vector2(c.X, OsuPlayfield.BASE_SIZE.Y - c.Y)));
s.ControlPoints = newControlPoints;
s.Curve?.Calculate(); // Recalculate the slider curve
});
}
}
public class OsuModSuddenDeath : ModSuddenDeath
+1 -1
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;
+2 -2
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)
{
+2 -12
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();
@@ -0,0 +1,35 @@
// 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.Input;
using osu.Game.Rulesets.Replays;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Replays
{
public class OsuReplayInputHandler : FramedReplayInputHandler
{
public OsuReplayInputHandler(Replay replay)
: base(replay)
{
}
public override List<InputState> GetPendingStates()
{
List<OsuAction> actions = new List<OsuAction>();
if (CurrentFrame?.MouseLeft ?? false) actions.Add(OsuAction.LeftButton);
if (CurrentFrame?.MouseRight ?? false) actions.Add(OsuAction.RightButton);
return new List<InputState>
{
new ReplayState<OsuAction>
{
Mouse = new ReplayMouseState(ToScreenSpace(Position ?? Vector2.Zero)),
PressedActions = actions
}
};
}
}
}
@@ -1,8 +1,11 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Configuration;
using osu.Framework.Extensions;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects;
@@ -14,6 +17,8 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
internal class OsuScoreProcessor : ScoreProcessor<OsuHitObject, OsuJudgement>
{
public readonly Bindable<ScoringMode> Mode = new Bindable<ScoringMode>(ScoringMode.Exponential);
public OsuScoreProcessor()
{
}
@@ -23,6 +28,35 @@ namespace osu.Game.Rulesets.Osu.Scoring
{
}
private float hpDrainRate;
private int totalAccurateJudgements;
private readonly Dictionary<OsuScoreResult, int> scoreResultCounts = new Dictionary<OsuScoreResult, int>();
private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>();
private double comboMaxScore;
protected override void ComputeTargets(Beatmap<OsuHitObject> beatmap)
{
hpDrainRate = beatmap.BeatmapInfo.Difficulty.DrainRate;
totalAccurateJudgements = beatmap.HitObjects.Count;
foreach (var h in beatmap.HitObjects)
{
if (h != null)
{
// TODO: add support for other object types.
AddJudgement(new OsuJudgement
{
MaxScore = OsuScoreResult.Hit300,
Score = OsuScoreResult.Hit300,
Result = HitResult.Hit
});
}
}
}
protected override void Reset()
{
base.Reset();
@@ -34,9 +68,6 @@ namespace osu.Game.Rulesets.Osu.Scoring
comboResultCounts.Clear();
}
private readonly Dictionary<OsuScoreResult, int> scoreResultCounts = new Dictionary<OsuScoreResult, int>();
private readonly Dictionary<ComboResult, int> comboResultCounts = new Dictionary<ComboResult, int>();
public override void PopulateScore(Score score)
{
base.PopulateScore(score);
@@ -57,28 +88,75 @@ namespace osu.Game.Rulesets.Osu.Scoring
comboResultCounts[judgement.Combo] = comboResultCounts.GetOrDefault(judgement.Combo) + 1;
}
switch (judgement.Result)
switch (judgement.Score)
{
case HitResult.Hit:
Health.Value += 0.1f;
case OsuScoreResult.Hit300:
Health.Value += (10.2 - hpDrainRate) * 0.02;
break;
case HitResult.Miss:
Health.Value -= 0.2f;
case OsuScoreResult.Hit100:
Health.Value += (8 - hpDrainRate) * 0.02;
break;
case OsuScoreResult.Hit50:
Health.Value += (4 - hpDrainRate) * 0.02;
break;
case OsuScoreResult.SliderTick:
Health.Value += Math.Max(7 - hpDrainRate, 0) * 0.01;
break;
case OsuScoreResult.Miss:
Health.Value -= hpDrainRate * 0.04;
break;
}
}
int score = 0;
int maxScore = 0;
calculateScore();
}
private void calculateScore()
{
int baseScore = 0;
double comboScore = 0;
int baseMaxScore = 0;
foreach (var j in Judgements)
{
score += j.ScoreValue;
maxScore += j.MaxScoreValue;
baseScore += j.ScoreValue;
baseMaxScore += j.MaxScoreValue;
comboScore += j.ScoreValue * (1 + Combo.Value / 10d);
}
TotalScore.Value = score;
Accuracy.Value = (double)score / maxScore;
Accuracy.Value = (double)baseScore / baseMaxScore;
if (comboScore > comboMaxScore)
comboMaxScore = comboScore;
if (baseScore == 0)
TotalScore.Value = 0;
else
{
// temporary to make scoring feel more like score v1 without being score v1.
float exponentialFactor = Mode.Value == ScoringMode.Exponential ? (float)Judgements.Count / 100 : 1;
TotalScore.Value =
(int)
(
exponentialFactor *
700000 * comboScore / comboMaxScore +
300000 * Math.Pow(Accuracy.Value, 10) * ((double)Judgements.Count / totalAccurateJudgements) +
0 /* bonusScore */
);
}
}
public enum ScoringMode
{
Standardised,
Exponential
}
}
}
@@ -10,9 +10,11 @@ using osu.Game.Rulesets.Osu.Beatmaps;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.Replays;
using osu.Game.Rulesets.Osu.Scoring;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Replays;
namespace osu.Game.Rulesets.Osu.UI
{
@@ -31,7 +33,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)
{
@@ -49,6 +51,8 @@ namespace osu.Game.Rulesets.Osu.UI
return null;
}
protected override FramedReplayInputHandler CreateReplayInputHandler(Replay replay) => new OsuReplayInputHandler(replay);
protected override Vector2 GetPlayfieldAspectAdjust() => new Vector2(0.75f);
}
}
@@ -78,6 +78,7 @@
<Compile Include="OsuDifficulty\Skills\Speed.cs" />
<Compile Include="OsuDifficulty\Utils\History.cs" />
<Compile Include="OsuInputManager.cs" />
<Compile Include="Replays\OsuReplayInputHandler.cs" />
<Compile Include="UI\Cursor\CursorTrail.cs" />
<Compile Include="UI\Cursor\GameplayCursor.cs" />
<Compile Include="UI\OsuSettings.cs" />
@@ -39,15 +39,22 @@ namespace osu.Game.Rulesets.Taiko.Beatmaps
/// </summary>
private const float taiko_base_distance = 100;
private readonly bool isForCurrentRuleset;
protected override IEnumerable<Type> ValidConversionTypes { get; } = new[] { typeof(HitObject) };
protected override Beatmap<TaikoHitObject> ConvertBeatmap(Beatmap original, bool isForCurrentRuleset)
public TaikoBeatmapConverter(bool isForCurrentRuleset)
{
this.isForCurrentRuleset = isForCurrentRuleset;
}
protected override Beatmap<TaikoHitObject> ConvertBeatmap(Beatmap original)
{
// Rewrite the beatmap info to add the slider velocity multiplier
BeatmapInfo info = original.BeatmapInfo.DeepClone();
info.Difficulty.SliderMultiplier *= legacy_velocity_multiplier;
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original, isForCurrentRuleset);
Beatmap<TaikoHitObject> converted = base.ConvertBeatmap(original);
// Post processing step to transform hit objects with the same start time into strong hits
converted.HitObjects = converted.HitObjects.GroupBy(t => t.StartTime).Select(x =>
@@ -81,7 +88,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 +101,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 +111,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 });
@@ -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)
{
}
}
}
}
@@ -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);
}
}
}
}
@@ -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)
@@ -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)
@@ -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)
{
@@ -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);
}
@@ -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,14 +28,6 @@ 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)
@@ -62,16 +53,23 @@ 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);
}
protected override void Update()
{
base.Update();
Size = BaseSize * Parent.RelativeChildSize;
}
protected override void UpdateState(ArmedState state)
{
var circlePiece = MainPiece as CirclePiece;
@@ -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);
}
}
}
@@ -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)
@@ -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)
@@ -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.
@@ -200,6 +199,8 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
{
base.Update();
Size = BaseSize * Parent.RelativeChildSize;
// Make the swell stop at the hit target
X = (float)Math.Max(Time.Current, HitObject.StartTime);
@@ -211,8 +212,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 +222,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);
@@ -1,28 +1,23 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using 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 Vector2 BaseSize;
protected readonly TaikoPiece MainPiece;
public new TaikoHitType HitObject;
@@ -36,7 +31,7 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
Origin = Anchor.Custom;
RelativeSizeAxes = Axes.Both;
Size = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
Size = BaseSize = new Vector2(HitObject.IsStrong ? TaikoHitObject.DEFAULT_STRONG_SIZE : TaikoHitObject.DEFAULT_SIZE);
Add(MainPiece = CreateMainPiece());
MainPiece.KiaiMode = HitObject.Kiai;
@@ -46,20 +41,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;
}
}
@@ -4,7 +4,6 @@
using osu.Game.Rulesets.Replays;
using System.Collections.Generic;
using osu.Framework.Input;
using OpenTK.Input;
namespace osu.Game.Rulesets.Taiko.Replays
{
@@ -17,21 +16,18 @@ namespace osu.Game.Rulesets.Taiko.Replays
public override List<InputState> GetPendingStates()
{
var keys = new List<Key>();
var actions = new List<TaikoAction>();
if (CurrentFrame?.MouseRight1 == true)
keys.Add(Key.F);
actions.Add(TaikoAction.LeftCentre);
if (CurrentFrame?.MouseRight2 == true)
keys.Add(Key.J);
actions.Add(TaikoAction.RightCentre);
if (CurrentFrame?.MouseLeft1 == true)
keys.Add(Key.D);
actions.Add(TaikoAction.LeftRim);
if (CurrentFrame?.MouseLeft2 == true)
keys.Add(Key.K);
actions.Add(TaikoAction.RightRim);
return new List<InputState>
{
new InputState { Keyboard = new ReplayKeyboardState(keys) }
};
return new List<InputState> { new ReplayState<TaikoAction> { PressedActions = actions } };
}
}
}
@@ -268,6 +268,7 @@ namespace osu.Game.Rulesets.Taiko.Scoring
base.Reset();
Health.Value = 0;
Accuracy.Value = 1;
bonusScore = 0;
comboPortion = 0;
@@ -135,6 +135,6 @@ namespace osu.Game.Rulesets.Taiko
return difficulty;
}
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter();
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(true);
}
}
}
@@ -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
}
}
+13 -10
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();
+13 -15
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;
}
}
}
+21 -7
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();
}
}
}
}
@@ -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
{
@@ -90,7 +91,9 @@ namespace osu.Game.Rulesets.Taiko.UI
public override ScoreProcessor CreateScoreProcessor() => new TaikoScoreProcessor(this);
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter();
protected override BeatmapConverter<TaikoHitObject> CreateBeatmapConverter() => new TaikoBeatmapConverter(IsForCurrentRuleset);
public override PassThroughInputManager CreateInputManager() => new TaikoInputManager(Ruleset.RulesetInfo);
protected override Playfield<TaikoHitObject, TaikoJudgement> CreatePlayfield() => new TaikoPlayfield
{
@@ -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" />
@@ -13,7 +13,6 @@ using osu.Framework.Platform;
using osu.Game.IPC;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Game.Rulesets;
namespace osu.Game.Tests.Beatmaps.IO
{
@@ -98,16 +97,14 @@ namespace osu.Game.Tests.Beatmaps.IO
private OsuGameBase loadOsu(GameHost host)
{
host.Storage.DeleteDatabase(@"client");
var osu = new OsuGameBase();
Task.Run(() => host.Run(osu));
while (!osu.IsLoaded)
Thread.Sleep(1);
//reset beatmap database (sqlite and storage backing)
osu.Dependencies.Get<RulesetStore>().Reset();
osu.Dependencies.Get<BeatmapManager>().Reset();
return osu;
}
+10 -1
View File
@@ -49,9 +49,18 @@ namespace osu.Game.Beatmaps
public string Path { get; set; }
[JsonProperty("file_md5")]
[JsonProperty("file_sha2")]
public string Hash { get; set; }
public bool Hidden { get; set; }
/// <summary>
/// MD5 is kept for legacy support (matching against replays, osu-web-10 etc.).
/// </summary>
[Indexed]
[JsonProperty("file_md5")]
public string MD5Hash { get; set; }
// General
public int AudioLeadIn { get; set; }
public bool Countdown { get; set; }
+41 -15
View File
@@ -33,11 +33,21 @@ namespace osu.Game.Beatmaps
/// </summary>
public event Action<BeatmapSetInfo> BeatmapSetAdded;
/// <summary>
/// Fired when a single difficulty has been hidden.
/// </summary>
public event Action<BeatmapInfo> BeatmapHidden;
/// <summary>
/// Fired when a <see cref="BeatmapSetInfo"/> is removed from the database.
/// </summary>
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
/// <summary>
/// Fired when a single difficulty has been restored.
/// </summary>
public event Action<BeatmapInfo> BeatmapRestored;
/// <summary>
/// A default representation of a WorkingBeatmap to use when no beatmap is available.
/// </summary>
@@ -71,6 +81,8 @@ namespace osu.Game.Beatmaps
beatmaps = new BeatmapStore(connection);
beatmaps.BeatmapSetAdded += s => BeatmapSetAdded?.Invoke(s);
beatmaps.BeatmapSetRemoved += s => BeatmapSetRemoved?.Invoke(s);
beatmaps.BeatmapHidden += b => BeatmapHidden?.Invoke(b);
beatmaps.BeatmapRestored += b => BeatmapRestored?.Invoke(b);
this.storage = storage;
this.files = files;
@@ -162,24 +174,34 @@ namespace osu.Game.Beatmaps
// If we have an ID then we already exist in the database.
if (beatmapSetInfo.ID != 0) return;
lock (beatmaps)
beatmaps.Add(beatmapSetInfo);
beatmaps.Add(beatmapSetInfo);
}
/// <summary>
/// Delete a beatmap from the manager.
/// Is a no-op for already deleted beatmaps.
/// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param>
/// <param name="beatmapSet">The beatmap set to delete.</param>
public void Delete(BeatmapSetInfo beatmapSet)
{
lock (beatmaps)
if (!beatmaps.Delete(beatmapSet)) return;
if (!beatmaps.Delete(beatmapSet)) return;
if (!beatmapSet.Protected)
files.Dereference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
}
/// <summary>
/// Delete a beatmap difficulty.
/// </summary>
/// <param name="beatmap">The beatmap difficulty to hide.</param>
public void Hide(BeatmapInfo beatmap) => beatmaps.Hide(beatmap);
/// <summary>
/// Restore a beatmap difficulty.
/// </summary>
/// <param name="beatmap">The beatmap difficulty to restore.</param>
public void Restore(BeatmapInfo beatmap) => beatmaps.Restore(beatmap);
/// <summary>
/// Returns a <see cref="BeatmapSetInfo"/> to a usable state if it has previously been deleted but not yet purged.
/// Is a no-op for already usable beatmaps.
@@ -187,8 +209,7 @@ namespace osu.Game.Beatmaps
/// <param name="beatmapSet">The beatmap to restore.</param>
public void Undelete(BeatmapSetInfo beatmapSet)
{
lock (beatmaps)
if (!beatmaps.Undelete(beatmapSet)) return;
if (!beatmaps.Undelete(beatmapSet)) return;
if (!beatmapSet.Protected)
files.Reference(beatmapSet.Files.Select(f => f.FileInfo).ToArray());
@@ -248,6 +269,13 @@ namespace osu.Game.Beatmaps
}
}
/// <summary>
/// Refresh an existing instance of a <see cref="BeatmapSetInfo"/> from the store.
/// </summary>
/// <param name="beatmapSet">A stale instance.</param>
/// <returns>A fresh instance.</returns>
public BeatmapSetInfo Refresh(BeatmapSetInfo beatmapSet) => QueryBeatmapSet(s => s.ID == beatmapSet.ID);
/// <summary>
/// Perform a lookup query on available <see cref="BeatmapSetInfo"/>s.
/// </summary>
@@ -255,7 +283,7 @@ namespace osu.Game.Beatmaps
/// <returns>Results from the provided query.</returns>
public List<BeatmapSetInfo> QueryBeatmapSets(Expression<Func<BeatmapSetInfo, bool>> query)
{
lock (beatmaps) return beatmaps.QueryAndPopulate(query);
return beatmaps.QueryAndPopulate(query);
}
/// <summary>
@@ -265,15 +293,12 @@ namespace osu.Game.Beatmaps
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public BeatmapInfo QueryBeatmap(Func<BeatmapInfo, bool> query)
{
lock (beatmaps)
{
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
BeatmapInfo set = beatmaps.Query<BeatmapInfo>().FirstOrDefault(query);
if (set != null)
beatmaps.Populate(set);
if (set != null)
beatmaps.Populate(set);
return set;
}
return set;
}
/// <summary>
@@ -372,6 +397,7 @@ namespace osu.Game.Beatmaps
beatmap.BeatmapInfo.Path = name;
beatmap.BeatmapInfo.Hash = ms.ComputeSHA2Hash();
beatmap.BeatmapInfo.MD5Hash = ms.ComputeMD5Hash();
// TODO: Diff beatmap metadata with set metadata and leave it here if necessary
beatmap.BeatmapInfo.Metadata = null;
+45 -2
View File
@@ -16,11 +16,14 @@ namespace osu.Game.Beatmaps
public event Action<BeatmapSetInfo> BeatmapSetAdded;
public event Action<BeatmapSetInfo> BeatmapSetRemoved;
public event Action<BeatmapInfo> BeatmapHidden;
public event Action<BeatmapInfo> BeatmapRestored;
/// <summary>
/// The current version of this store. Used for migrations (see <see cref="PerformMigration(int, int)"/>).
/// The initial version is 1.
/// </summary>
protected override int StoreVersion => 2;
protected override int StoreVersion => 4;
public BeatmapStore(SQLiteConnection connection)
: base(connection)
@@ -77,6 +80,14 @@ namespace osu.Game.Beatmaps
// cannot migrate; breaking underlying changes.
Reset();
break;
case 3:
// Added MD5Hash column to BeatmapInfo
Connection.MigrateTable<BeatmapInfo>();
break;
case 4:
// Added Hidden column to BeatmapInfo
Connection.MigrateTable<BeatmapInfo>();
break;
}
}
}
@@ -96,7 +107,7 @@ namespace osu.Game.Beatmaps
}
/// <summary>
/// Delete a <see cref="BeatmapSetInfo"/> to the database.
/// Delete a <see cref="BeatmapSetInfo"/> from the database.
/// </summary>
/// <param name="beatmapSet">The beatmap to delete.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapSetInfo.DeletePending"/> was changed.</returns>
@@ -127,6 +138,38 @@ namespace osu.Game.Beatmaps
return true;
}
/// <summary>
/// Hide a <see cref="BeatmapInfo"/> in the database.
/// </summary>
/// <param name="beatmap">The beatmap to hide.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
public bool Hide(BeatmapInfo beatmap)
{
if (beatmap.Hidden) return false;
beatmap.Hidden = true;
Connection.Update(beatmap);
BeatmapHidden?.Invoke(beatmap);
return true;
}
/// <summary>
/// Restore a previously hidden <see cref="BeatmapInfo"/>.
/// </summary>
/// <param name="beatmap">The beatmap to restore.</param>
/// <returns>Whether the beatmap's <see cref="BeatmapInfo.Hidden"/> was changed.</returns>
public bool Restore(BeatmapInfo beatmap)
{
if (!beatmap.Hidden) return false;
beatmap.Hidden = false;
Connection.Update(beatmap);
BeatmapRestored?.Invoke(beatmap);
return true;
}
private void cleanupPendingDeletions()
{
Connection.RunInTransaction(() =>
@@ -10,4 +10,4 @@ namespace osu.Game.Beatmaps.ControlPoints
/// </summary>
public double SpeedMultiplier = 1;
}
}
}
+5 -1
View File
@@ -30,11 +30,15 @@ namespace osu.Game.Beatmaps
public abstract class DifficultyCalculator<T> : DifficultyCalculator where T : HitObject
{
protected readonly Beatmap Beatmap;
protected List<T> Objects;
protected DifficultyCalculator(Beatmap beatmap)
{
Objects = CreateBeatmapConverter().Convert(beatmap, true).HitObjects;
Beatmap = beatmap;
Objects = CreateBeatmapConverter().Convert(beatmap).HitObjects;
foreach (var h in Objects)
h.ApplyDefaults(beatmap.ControlPointInfo, beatmap.BeatmapInfo.Difficulty);
+18 -4
View File
@@ -11,6 +11,8 @@ namespace osu.Game.Beatmaps.Drawables
{
public class BeatmapGroup : IStateful<BeatmapGroupState>
{
public event Action<BeatmapGroupState> StateChanged;
public BeatmapPanel SelectedPanel;
/// <summary>
@@ -23,19 +25,26 @@ namespace osu.Game.Beatmaps.Drawables
/// </summary>
public Action<BeatmapInfo> StartRequested;
public BeatmapSetHeader Header;
public Action<BeatmapSetInfo> DeleteRequested;
private BeatmapGroupState state;
public Action<BeatmapSetInfo> RestoreHiddenRequested;
public Action<BeatmapInfo> HideDifficultyRequested;
public BeatmapSetHeader Header;
public List<BeatmapPanel> BeatmapPanels;
public BeatmapSetInfo BeatmapSet;
private BeatmapGroupState state;
public BeatmapGroupState State
{
get { return state; }
set
{
state = value;
switch (value)
{
case BeatmapGroupState.Expanded:
@@ -54,7 +63,8 @@ namespace osu.Game.Beatmaps.Drawables
panel.State = PanelSelectedState.Hidden;
break;
}
state = value;
StateChanged?.Invoke(state);
}
}
@@ -66,14 +76,17 @@ namespace osu.Game.Beatmaps.Drawables
Header = new BeatmapSetHeader(beatmap)
{
GainedSelection = headerGainedSelection,
DeleteRequested = b => DeleteRequested(b),
RestoreHiddenRequested = b => RestoreHiddenRequested(b),
RelativeSizeAxes = Axes.X,
};
BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.OrderBy(b => b.StarDifficulty).ToList();
BeatmapSet.Beatmaps = BeatmapSet.Beatmaps.Where(b => !b.Hidden).OrderBy(b => b.StarDifficulty).ToList();
BeatmapPanels = BeatmapSet.Beatmaps.Select(b => new BeatmapPanel(b)
{
Alpha = 0,
GainedSelection = panelGainedSelection,
HideRequested = p => HideDifficultyRequested?.Invoke(p),
StartRequested = p => { StartRequested?.Invoke(p.Beatmap); },
RelativeSizeAxes = Axes.X,
}).ToList();
@@ -81,6 +94,7 @@ namespace osu.Game.Beatmaps.Drawables
Header.AddDifficultyIcons(BeatmapPanels);
}
private void headerGainedSelection(BeatmapSetHeader panel)
{
State = BeatmapGroupState.Expanded;
+13 -1
View File
@@ -5,6 +5,7 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Game.Graphics;
using osu.Game.Graphics.Backgrounds;
@@ -14,16 +15,20 @@ using OpenTK.Graphics;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Beatmaps.Drawables
{
public class BeatmapPanel : Panel
public class BeatmapPanel : Panel, IHasContextMenu
{
public BeatmapInfo Beatmap;
private readonly Sprite background;
public Action<BeatmapPanel> GainedSelection;
public Action<BeatmapPanel> StartRequested;
public Action<BeatmapPanel> EditRequested;
public Action<BeatmapInfo> HideRequested;
private readonly Triangles triangles;
private readonly StarCounter starCounter;
@@ -148,5 +153,12 @@ namespace osu.Game.Beatmaps.Drawables
}
};
}
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("Play", MenuItemType.Highlighted, () => StartRequested?.Invoke(this)),
new OsuMenuItem("Edit", MenuItemType.Standard, () => EditRequested?.Invoke(this)),
new OsuMenuItem("Hide", MenuItemType.Destructive, () => HideRequested?.Invoke(Beatmap)),
};
}
}
@@ -3,22 +3,31 @@
using System;
using System.Collections.Generic;
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Localisation;
using osu.Game.Graphics.Sprites;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
namespace osu.Game.Beatmaps.Drawables
{
public class BeatmapSetHeader : Panel
public class BeatmapSetHeader : Panel, IHasContextMenu
{
public Action<BeatmapSetHeader> GainedSelection;
public Action<BeatmapSetInfo> DeleteRequested;
public Action<BeatmapSetInfo> RestoreHiddenRequested;
private readonly SpriteText title;
private readonly SpriteText artist;
@@ -148,5 +157,23 @@ namespace osu.Game.Beatmaps.Drawables
foreach (var p in panels)
difficultyIcons.Add(new DifficultyIcon(p.Beatmap));
}
public MenuItem[] ContextMenuItems
{
get
{
List<MenuItem> items = new List<MenuItem>();
if (State == PanelSelectedState.NotSelected)
items.Add(new OsuMenuItem("Expand", MenuItemType.Highlighted, () => State = PanelSelectedState.Selected));
if (beatmap.BeatmapSetInfo.Beatmaps.Any(b => b.Hidden))
items.Add(new OsuMenuItem("Restore all hidden", MenuItemType.Standard, () => RestoreHiddenRequested?.Invoke(beatmap.BeatmapSetInfo)));
items.Add(new OsuMenuItem("Delete", MenuItemType.Destructive, () => DeleteRequested?.Invoke(beatmap.BeatmapSetInfo)));
return items.ToArray();
}
}
}
}
@@ -33,7 +33,8 @@ namespace osu.Game.Beatmaps.Drawables
Normal,
Hard,
Insane,
Expert
Expert,
ExpertPlus
}
private DifficultyRating getDifficultyRating(BeatmapInfo beatmap)
@@ -44,7 +45,8 @@ namespace osu.Game.Beatmaps.Drawables
if (rating < 2.25) return DifficultyRating.Normal;
if (rating < 3.75) return DifficultyRating.Hard;
if (rating < 5.25) return DifficultyRating.Insane;
return DifficultyRating.Expert;
if (rating < 6.75) return DifficultyRating.Expert;
return DifficultyRating.ExpertPlus;
}
private Color4 getColour(BeatmapInfo beatmap)
@@ -55,12 +57,14 @@ namespace osu.Game.Beatmaps.Drawables
return palette.Green;
default:
case DifficultyRating.Normal:
return palette.Yellow;
return palette.Blue;
case DifficultyRating.Hard:
return palette.Pink;
return palette.Yellow;
case DifficultyRating.Insane:
return palette.Purple;
return palette.Pink;
case DifficultyRating.Expert:
return palette.Purple;
case DifficultyRating.ExpertPlus:
return palette.Gray0;
}
}
+8 -1
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 osu.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -15,6 +16,8 @@ namespace osu.Game.Beatmaps.Drawables
{
public const float MAX_HEIGHT = 80;
public event Action<PanelSelectedState> StateChanged;
public override bool RemoveWhenNotAlive => false;
private readonly Container nestedContainer;
@@ -77,11 +80,15 @@ namespace osu.Game.Beatmaps.Drawables
set
{
if (state == value) return;
if (state == value)
return;
var last = state;
state = value;
ApplyState(last);
StateChanged?.Invoke(State);
}
}
-3
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)
{
@@ -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)
+4 -1
View File
@@ -70,6 +70,8 @@ namespace osu.Game.Configuration
// Update
Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
Set(OsuSetting.Version, string.Empty);
}
public OsuConfigManager(Storage storage) : base(storage)
@@ -106,6 +108,7 @@ namespace osu.Game.Configuration
SnakingInSliders,
SnakingOutSliders,
ShowFpsDisplay,
ChatDisplayHeight
ChatDisplayHeight,
Version
}
}
@@ -19,12 +19,12 @@ namespace osu.Game.Graphics.Containers
samplePopIn = audio.Sample.Get(@"UI/melodic-5");
samplePopOut = audio.Sample.Get(@"UI/melodic-4");
StateChanged += OsuFocusedOverlayContainer_StateChanged;
StateChanged += onStateChanged;
}
private void OsuFocusedOverlayContainer_StateChanged(VisibilityContainer arg1, Visibility arg2)
private void onStateChanged(Visibility visibility)
{
switch (arg2)
switch (visibility)
{
case Visibility.Visible:
samplePopIn?.Play();
+21 -8
View File
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Sprites;
using osu.Framework.Input;
using osu.Game.Configuration;
using System;
using System.Diagnostics;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Graphics.Cursor
@@ -21,20 +22,31 @@ namespace osu.Game.Graphics.Cursor
private bool dragging;
private bool startRotation;
protected override bool OnMouseMove(InputState state)
{
if (dragging)
{
Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown ?? state.Mouse.Delta;
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
Debug.Assert(state.Mouse.PositionMouseDown != null);
// Always rotate in the direction of least distance
float diff = (degrees - ActiveCursor.Rotation) % 360;
if (diff < -180) diff += 360;
if (diff > 180) diff -= 360;
degrees = ActiveCursor.Rotation + diff;
// don't start rotating until we're moved a minimum distance away from the mouse down location,
// else it can have an annoying effect.
startRotation |= Vector2.Distance(state.Mouse.Position, state.Mouse.PositionMouseDown.Value) > 30;
ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint);
if (startRotation)
{
Vector2 offset = state.Mouse.Position - state.Mouse.PositionMouseDown.Value;
float degrees = (float)MathHelper.RadiansToDegrees(Math.Atan2(-offset.X, offset.Y)) + 24.3f;
// Always rotate in the direction of least distance
float diff = (degrees - ActiveCursor.Rotation) % 360;
if (diff < -180) diff += 360;
if (diff > 180) diff -= 360;
degrees = ActiveCursor.Rotation + diff;
ActiveCursor.RotateTo(degrees, 600, Easing.OutQuint);
}
}
return base.OnMouseMove(state);
@@ -61,6 +73,7 @@ namespace osu.Game.Graphics.Cursor
if (!state.Mouse.HasMainButtonPressed)
{
dragging = false;
startRotation = false;
((Cursor)ActiveCursor).AdditiveLayer.FadeOut(500, Easing.OutQuint);
ActiveCursor.RotateTo(0, 600 * (1 + Math.Abs(ActiveCursor.Rotation / 720)), Easing.OutElasticHalf);
@@ -9,6 +9,6 @@ namespace osu.Game.Graphics.Cursor
{
public class OsuContextMenuContainer : ContextMenuContainer
{
protected override ContextMenu<ContextMenuItem> CreateContextMenu() => new OsuContextMenu<ContextMenuItem>();
protected override Menu CreateMenu() => new OsuContextMenu();
}
}
@@ -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 OpenTK;
using osu.Framework;
using osu.Framework.Graphics;
@@ -35,6 +36,8 @@ namespace osu.Game.Graphics.UserInterface
private class BreadcrumbTabItem : OsuTabItem, IStateful<Visibility>
{
public event Action<Visibility> StateChanged;
public readonly SpriteIcon Chevron;
//don't allow clicking between transitions and don't make the chevron clickable
@@ -42,6 +45,7 @@ namespace osu.Game.Graphics.UserInterface
public override bool HandleInput => State == Visibility.Visible;
private Visibility state;
public Visibility State
{
get { return state; }
@@ -62,6 +66,8 @@ namespace osu.Game.Graphics.UserInterface
this.FadeOut(transition_duration, Easing.OutQuint);
this.ScaleTo(new Vector2(0.8f, 1f), transition_duration, Easing.OutQuint);
}
StateChanged?.Invoke(State);
}
}
+4 -2
View File
@@ -24,6 +24,8 @@ namespace osu.Game.Graphics.UserInterface
private SampleChannel sampleClick;
private SampleChannel sampleHover;
protected Triangles Triangles;
public OsuButton()
{
Height = 40;
@@ -52,7 +54,7 @@ namespace osu.Game.Graphics.UserInterface
AddRange(new Drawable[]
{
new Triangles
Triangles = new Triangles
{
RelativeSizeAxes = Axes.Both,
ColourDark = colours.BlueDarker,
@@ -120,4 +122,4 @@ namespace osu.Game.Graphics.UserInterface
}
}
}
}
}
@@ -1,52 +1,39 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
namespace osu.Game.Graphics.UserInterface
{
public class OsuContextMenu<TItem> : ContextMenu<TItem>
where TItem : ContextMenuItem
public class OsuContextMenu : OsuMenu
{
protected override Menu<TItem> CreateMenu() => new CustomMenu();
private const int fade_duration = 250;
public class CustomMenu : Menu<TItem>
public OsuContextMenu()
: base(Direction.Vertical)
{
private const int fade_duration = 250;
public CustomMenu()
MaskingContainer.CornerRadius = 5;
MaskingContainer.EdgeEffect = new EdgeEffectParameters
{
CornerRadius = 5;
ItemsContainer.Padding = new MarginPadding { Vertical = OsuContextMenuItem.MARGIN_VERTICAL };
Masking = true;
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.1f),
Radius = 4,
};
}
Type = EdgeEffectType.Shadow,
Colour = Color4.Black.Opacity(0.1f),
Radius = 4,
};
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Background.Colour = colours.ContextMenuGray;
}
protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint);
protected override void UpdateContentHeight()
{
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
this.ResizeTo(new Vector2(1, State == MenuState.Opened ? actualHeight : 0), 300, Easing.OutQuint);
}
ItemsContainer.Padding = new MarginPadding { Vertical = DrawableOsuMenuItem.MARGIN_VERTICAL };
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = colours.ContextMenuGray;
}
protected override void AnimateOpen() => this.FadeIn(fade_duration, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(fade_duration, Easing.OutQuint);
}
}
@@ -1,114 +0,0 @@
// 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.Graphics;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
namespace osu.Game.Graphics.UserInterface
{
public class OsuContextMenuItem : ContextMenuItem
{
private const int transition_length = 80;
private const int margin_horizontal = 17;
public const int MARGIN_VERTICAL = 4;
private const int text_size = 17;
private OsuSpriteText text;
private OsuSpriteText textBold;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private readonly MenuItemType type;
protected override Container CreateTextContainer(string title) => new Container
{
AutoSizeAxes = Axes.Both,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
Children = new Drawable[]
{
text = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Text = title,
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
},
textBold = new OsuSpriteText
{
AlwaysPresent = true,
Alpha = 0,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Text = title,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
}
}
};
public OsuContextMenuItem(string title, MenuItemType type = MenuItemType.Standard) : base(title)
{
this.type = type;
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Sample.Get(@"UI/generic-hover");
sampleClick = audio.Sample.Get(@"UI/generic-click");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
updateTextColour();
}
private void updateTextColour()
{
switch (type)
{
case MenuItemType.Standard:
textBold.Colour = text.Colour = Color4.White;
break;
case MenuItemType.Destructive:
textBold.Colour = text.Colour = Color4.Red;
break;
case MenuItemType.Highlighted:
textBold.Colour = text.Colour = OsuColour.FromHex(@"ffcc22");
break;
}
}
protected override bool OnHover(InputState state)
{
sampleHover.Play();
textBold.FadeIn(transition_length, Easing.OutQuint);
text.FadeOut(transition_length, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
textBold.FadeOut(transition_length, Easing.OutQuint);
text.FadeIn(transition_length, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
sampleClick.Play();
return base.OnClick(state);
}
}
}
+152 -74
View File
@@ -14,107 +14,185 @@ using OpenTK;
namespace osu.Game.Graphics.UserInterface
{
public class OsuDropdown<T> : Dropdown<T>
public class OsuDropdown<T> : Dropdown<T>, IHasAccentColour
{
protected override DropdownHeader CreateHeader() => new OsuDropdownHeader { AccentColour = AccentColour };
protected override Menu CreateMenu() => new OsuMenu();
private Color4? accentColour;
public virtual Color4 AccentColour
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour.GetValueOrDefault(); }
get { return accentColour; }
set
{
accentColour = value;
if (Header != null)
((OsuDropdownHeader)Header).AccentColour = value;
foreach (var item in MenuItems.OfType<OsuDropdownMenuItem>())
item.AccentColour = value;
updateAccentColour();
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
if (accentColour == null)
AccentColour = colours.PinkDarker;
if (accentColour == default(Color4))
accentColour = colours.PinkDarker;
updateAccentColour();
}
protected override DropdownMenuItem<T> CreateMenuItem(string text, T value) => new OsuDropdownMenuItem(text, value) { AccentColour = AccentColour };
public class OsuDropdownMenuItem : DropdownMenuItem<T>
private void updateAccentColour()
{
public OsuDropdownMenuItem(string text, T current) : base(text, current)
var header = Header as IHasAccentColour;
if (header != null) header.AccentColour = accentColour;
var menu = Menu as IHasAccentColour;
if (menu != null) menu.AccentColour = accentColour;
}
protected override DropdownHeader CreateHeader() => new OsuDropdownHeader();
protected override DropdownMenu CreateMenu() => new OsuDropdownMenu();
#region OsuDropdownMenu
protected class OsuDropdownMenu : DropdownMenu, IHasAccentColour
{
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
public OsuDropdownMenu()
{
Foreground.Padding = new MarginPadding(2);
CornerRadius = 4;
BackgroundColour = Color4.Black.Opacity(0.5f);
Masking = true;
CornerRadius = 6;
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
ItemsContainer.Padding = new MarginPadding(5);
}
Children = new[]
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint);
// todo: this uses the same styling as OsuMenu. hopefully we can just use OsuMenu in the future with some refactoring
protected override void UpdateSize(Vector2 newSize)
{
if (Direction == Direction.Vertical)
{
new FillFlowContainer
Width = newSize.X;
this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint);
}
else
{
Direction = FillDirection.Horizontal,
RelativeSizeAxes = Axes.X,
AutoSizeAxes = Axes.Y,
Children = new Drawable[]
{
Chevron = new SpriteIcon
{
AlwaysPresent = true,
Icon = FontAwesome.fa_chevron_right,
Colour = Color4.Black,
Alpha = 0.5f,
Size = new Vector2(8),
Margin = new MarginPadding { Left = 3, Right = 3 },
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
},
Label = new OsuSpriteText {
Text = text,
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
}
}
Height = newSize.Y;
this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint);
}
};
}
private Color4? accentColour;
protected readonly SpriteIcon Chevron;
protected readonly OsuSpriteText Label;
protected override void FormatForeground(bool hover = false)
{
base.FormatForeground(hover);
Chevron.Alpha = hover ? 1 : 0;
}
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour.GetValueOrDefault(); }
get { return accentColour; }
set
{
accentColour = value;
BackgroundColourHover = BackgroundColourSelected = value;
FormatBackground();
FormatForeground();
foreach (var c in Children.OfType<IHasAccentColour>())
c.AccentColour = value;
}
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = Color4.Transparent;
BackgroundColourHover = accentColour ?? colours.PinkDarker;
BackgroundColourSelected = Color4.Black.Opacity(0.5f);
}
}
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuDropdownMenuItem(item) { AccentColour = accentColour };
public class OsuDropdownHeader : DropdownHeader
#region DrawableOsuDropdownMenuItem
protected class DrawableOsuDropdownMenuItem : DrawableDropdownMenuItem, IHasAccentColour
{
private Color4? accentColour;
public Color4 AccentColour
{
get { return accentColour ?? nonAccentSelectedColour; }
set
{
accentColour = value;
updateColours();
}
}
private void updateColours()
{
BackgroundColourHover = accentColour ?? nonAccentHoverColour;
BackgroundColourSelected = accentColour ?? nonAccentSelectedColour;
UpdateBackgroundColour();
UpdateForegroundColour();
}
private Color4 nonAccentHoverColour;
private Color4 nonAccentSelectedColour;
public DrawableOsuDropdownMenuItem(MenuItem item)
: base(item)
{
Foreground.Padding = new MarginPadding(2);
Masking = true;
CornerRadius = 6;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
BackgroundColour = Color4.Transparent;
nonAccentHoverColour = colours.PinkDarker;
nonAccentSelectedColour = Color4.Black.Opacity(0.5f);
updateColours();
}
protected override void UpdateForegroundColour()
{
base.UpdateForegroundColour();
var content = Foreground.Children.FirstOrDefault() as Content;
if (content != null) content.Chevron.Alpha = IsHovered ? 1 : 0;
}
protected override Drawable CreateContent() => new Content();
protected new class Content : FillFlowContainer, IHasText
{
public string Text
{
get { return Label.Text; }
set { Label.Text = value; }
}
public readonly OsuSpriteText Label;
public readonly SpriteIcon Chevron;
public Content()
{
RelativeSizeAxes = Axes.X;
AutoSizeAxes = Axes.Y;
Direction = FillDirection.Horizontal;
Children = new Drawable[]
{
Chevron = new SpriteIcon
{
AlwaysPresent = true,
Icon = FontAwesome.fa_chevron_right,
Colour = Color4.Black,
Alpha = 0.5f,
Size = new Vector2(8),
Margin = new MarginPadding { Left = 3, Right = 3 },
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
},
Label = new OsuSpriteText
{
Origin = Anchor.CentreLeft,
Anchor = Anchor.CentreLeft,
}
};
}
}
}
#endregion
}
#endregion
public class OsuDropdownHeader : DropdownHeader, IHasAccentColour
{
protected readonly SpriteText Text;
protected override string Label
@@ -125,14 +203,14 @@ namespace osu.Game.Graphics.UserInterface
protected readonly SpriteIcon Icon;
private Color4? accentColour;
private Color4 accentColour;
public virtual Color4 AccentColour
{
get { return accentColour.GetValueOrDefault(); }
get { return accentColour; }
set
{
accentColour = value;
BackgroundColourHover = value;
BackgroundColourHover = accentColour;
}
}
@@ -167,7 +245,7 @@ namespace osu.Game.Graphics.UserInterface
private void load(OsuColour colours)
{
BackgroundColour = Color4.Black.Opacity(0.5f);
BackgroundColourHover = accentColour ?? colours.PinkDarker;
BackgroundColourHover = colours.PinkDarker;
}
}
}
+141 -8
View File
@@ -1,32 +1,165 @@
// Copyright (c) 2007-2017 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
using OpenTK;
namespace osu.Game.Graphics.UserInterface
{
public class OsuMenu : Menu
{
public OsuMenu()
public OsuMenu(Direction direction)
: base(direction)
{
CornerRadius = 4;
Background.Colour = Color4.Black.Opacity(0.5f);
BackgroundColour = Color4.Black.Opacity(0.5f);
MaskingContainer.CornerRadius = 4;
ItemsContainer.Padding = new MarginPadding(5);
}
protected override void AnimateOpen() => this.FadeIn(300, Easing.OutQuint);
protected override void AnimateClose() => this.FadeOut(300, Easing.OutQuint);
protected override void UpdateContentHeight()
protected override void UpdateSize(Vector2 newSize)
{
var actualHeight = (RelativeSizeAxes & Axes.Y) > 0 ? 1 : ContentHeight;
this.ResizeTo(new Vector2(1, State == MenuState.Opened ? actualHeight : 0), 300, Easing.OutQuint);
if (Direction == Direction.Vertical)
{
Width = newSize.X;
this.ResizeHeightTo(newSize.Y, 300, Easing.OutQuint);
}
else
{
Height = newSize.Y;
this.ResizeWidthTo(newSize.X, 300, Easing.OutQuint);
}
}
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuMenuItem(item);
protected class DrawableOsuMenuItem : DrawableMenuItem
{
private const int margin_horizontal = 17;
private const int text_size = 17;
private const int transition_length = 80;
public const int MARGIN_VERTICAL = 4;
private SampleChannel sampleClick;
private SampleChannel sampleHover;
private TextContainer text;
public DrawableOsuMenuItem(MenuItem item)
: base(item)
{
}
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleHover = audio.Sample.Get(@"UI/generic-hover");
sampleClick = audio.Sample.Get(@"UI/generic-click");
BackgroundColour = Color4.Transparent;
BackgroundColourHover = OsuColour.FromHex(@"172023");
updateTextColour();
}
private void updateTextColour()
{
switch ((Item as OsuMenuItem)?.Type)
{
default:
case MenuItemType.Standard:
text.Colour = Color4.White;
break;
case MenuItemType.Destructive:
text.Colour = Color4.Red;
break;
case MenuItemType.Highlighted:
text.Colour = OsuColour.FromHex(@"ffcc22");
break;
}
}
protected override bool OnHover(InputState state)
{
sampleHover.Play();
text.BoldText.FadeIn(transition_length, Easing.OutQuint);
text.NormalText.FadeOut(transition_length, Easing.OutQuint);
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
text.BoldText.FadeOut(transition_length, Easing.OutQuint);
text.NormalText.FadeIn(transition_length, Easing.OutQuint);
base.OnHoverLost(state);
}
protected override bool OnClick(InputState state)
{
sampleClick.Play();
return base.OnClick(state);
}
protected override Drawable CreateContent() => text = new TextContainer();
private class TextContainer : Container, IHasText
{
public string Text
{
get { return NormalText.Text; }
set
{
NormalText.Text = value;
BoldText.Text = value;
}
}
public readonly SpriteText NormalText;
public readonly SpriteText BoldText;
public TextContainer()
{
Anchor = Anchor.CentreLeft;
Origin = Anchor.CentreLeft;
AutoSizeAxes = Axes.Both;
Children = new Drawable[]
{
NormalText = new OsuSpriteText
{
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
},
BoldText = new OsuSpriteText
{
AlwaysPresent = true,
Alpha = 0,
Anchor = Anchor.CentreLeft,
Origin = Anchor.CentreLeft,
TextSize = text_size,
Font = @"Exo2.0-Bold",
Margin = new MarginPadding { Horizontal = margin_horizontal, Vertical = MARGIN_VERTICAL },
}
};
}
}
}
}
}
@@ -0,0 +1,25 @@
// 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.UserInterface;
namespace osu.Game.Graphics.UserInterface
{
public class OsuMenuItem : MenuItem
{
public readonly MenuItemType Type;
public OsuMenuItem(string text, MenuItemType type = MenuItemType.Standard)
: base(text)
{
Type = type;
}
public OsuMenuItem(string text, MenuItemType type, Action action)
: base(text, action)
{
Type = type;
}
}
}
@@ -1,11 +1,16 @@
// 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.Graphics;
using osu.Framework.Graphics.Containers;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Cursor;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Platform;
namespace osu.Game.Graphics.UserInterface
{
@@ -15,6 +20,49 @@ namespace osu.Game.Graphics.UserInterface
public override bool AllowClipboardExport => false;
private readonly CapsWarning warning;
private GameHost host;
public OsuPasswordTextBox()
{
Add(warning = new CapsWarning
{
Size = new Vector2(20),
Origin = Anchor.CentreRight,
Anchor = Anchor.CentreRight,
Margin = new MarginPadding { Right = 10 },
Alpha = 0,
});
}
[BackgroundDependencyLoader]
private void load(GameHost host)
{
this.host = host;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
if (args.Key == Key.CapsLock)
updateCapsWarning(host.CapsLockEnabled);
return base.OnKeyDown(state, args);
}
protected override void OnFocus(InputState state)
{
updateCapsWarning(host.CapsLockEnabled);
base.OnFocus(state);
}
protected override void OnFocusLost(InputState state)
{
updateCapsWarning(false);
base.OnFocusLost(state);
}
private void updateCapsWarning(bool visible) => warning.FadeTo(visible ? 1 : 0, 250, Easing.OutQuint);
public class PasswordMaskChar : Container
{
private readonly CircularContainer circle;
@@ -51,5 +99,21 @@ namespace osu.Game.Graphics.UserInterface
circle.ResizeTo(new Vector2(0.8f), 500, Easing.OutQuint);
}
}
private class CapsWarning : SpriteIcon, IHasTooltip
{
public string TooltipText => @"Caps lock is active";
public CapsWarning()
{
Icon = FontAwesome.fa_warning;
}
[BackgroundDependencyLoader]
private void load(OsuColour colour)
{
Colour = colour.YellowLight;
}
}
}
}
@@ -37,34 +37,34 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
if (accentColour == null)
if (accentColour == default(Color4))
AccentColour = colours.Blue;
}
private Color4? accentColour;
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour.GetValueOrDefault(); }
get { return accentColour; }
set
{
accentColour = value;
var dropDown = Dropdown as OsuTabDropdown;
if (dropDown != null)
dropDown.AccentColour = value;
foreach (var item in TabContainer.Children.OfType<OsuTabItem>())
item.AccentColour = value;
var dropdown = Dropdown as IHasAccentColour;
if (dropdown != null)
dropdown.AccentColour = value;
foreach (var i in TabContainer.Children.OfType<IHasAccentColour>())
i.AccentColour = value;
}
}
public class OsuTabItem : TabItem<T>
public class OsuTabItem : TabItem<T>, IHasAccentColour
{
protected readonly SpriteText Text;
private readonly Box box;
private Color4? accentColour;
private Color4 accentColour;
public Color4 AccentColour
{
get { return accentColour.GetValueOrDefault(); }
get { return accentColour; }
set
{
accentColour = value;
@@ -103,7 +103,7 @@ namespace osu.Game.Graphics.UserInterface
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
if (accentColour == null)
if (accentColour == default(Color4))
AccentColour = colours.Blue;
}
@@ -140,38 +140,55 @@ namespace osu.Game.Graphics.UserInterface
protected override void OnDeactivated() => fadeInactive();
}
// todo: this needs to go
private class OsuTabDropdown : OsuDropdown<T>
{
protected override DropdownHeader CreateHeader() => new OsuTabDropdownHeader
{
AccentColour = AccentColour,
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight,
};
protected override DropdownMenuItem<T> CreateMenuItem(string text, T value)
{
var item = base.CreateMenuItem(text, value);
item.ForegroundColourHover = Color4.Black;
return item;
}
public OsuTabDropdown()
{
DropdownMenu.Anchor = Anchor.TopRight;
DropdownMenu.Origin = Anchor.TopRight;
RelativeSizeAxes = Axes.X;
DropdownMenu.Background.Colour = Color4.Black.Opacity(0.7f);
DropdownMenu.MaxHeight = 400;
}
protected override DropdownMenu CreateMenu() => new OsuTabDropdownMenu();
protected override DropdownHeader CreateHeader() => new OsuTabDropdownHeader
{
Anchor = Anchor.TopRight,
Origin = Anchor.TopRight
};
private class OsuTabDropdownMenu : OsuDropdownMenu
{
public OsuTabDropdownMenu()
{
Anchor = Anchor.TopRight;
Origin = Anchor.TopRight;
BackgroundColour = Color4.Black.Opacity(0.7f);
MaxHeight = 400;
}
protected override DrawableMenuItem CreateDrawableMenuItem(MenuItem item) => new DrawableOsuTabDropdownMenuItem(item) { AccentColour = AccentColour };
private class DrawableOsuTabDropdownMenuItem : DrawableOsuDropdownMenuItem
{
public DrawableOsuTabDropdownMenuItem(MenuItem item)
: base(item)
{
ForegroundColourHover = Color4.Black;
}
}
}
protected class OsuTabDropdownHeader : OsuDropdownHeader
{
public override Color4 AccentColour
{
get { return base.AccentColour; }
get
{
return base.AccentColour;
}
set
{
base.AccentColour = value;
@@ -179,18 +196,6 @@ namespace osu.Game.Graphics.UserInterface
}
}
protected override bool OnHover(InputState state)
{
Foreground.Colour = BackgroundColour;
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
Foreground.Colour = BackgroundColourHover;
base.OnHoverLost(state);
}
public OsuTabDropdownHeader()
{
RelativeSizeAxes = Axes.None;
@@ -220,6 +225,18 @@ namespace osu.Game.Graphics.UserInterface
Padding = new MarginPadding { Left = 5, Right = 5 };
}
protected override bool OnHover(InputState state)
{
Foreground.Colour = BackgroundColour;
return base.OnHover(state);
}
protected override void OnHoverLost(InputState state)
{
Foreground.Colour = BackgroundColourHover;
base.OnHoverLost(state);
}
}
}
}
@@ -0,0 +1,67 @@
// 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;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using OpenTK.Graphics;
namespace osu.Game.Graphics.UserInterface
{
public class ProgressBar : SliderBar<double>
{
public Action<double> OnSeek;
private readonly Box fill;
private readonly Box background;
public Color4 FillColour
{
set { fill.Colour = value; }
}
public Color4 BackgroundColour
{
set
{
background.Alpha = 1;
background.Colour = value;
}
}
public double EndTime
{
set { CurrentNumber.MaxValue = value; }
}
public double CurrentTime
{
set { CurrentNumber.Value = value; }
}
public ProgressBar()
{
CurrentNumber.MinValue = 0;
CurrentNumber.MaxValue = 1;
RelativeSizeAxes = Axes.X;
Children = new Drawable[]
{
background = new Box
{
Alpha = 0,
RelativeSizeAxes = Axes.Both
},
fill = new Box { RelativeSizeAxes = Axes.Y }
};
}
protected override void UpdateValue(float value)
{
fill.Width = value * UsableWidth;
}
protected override void OnUserChange() => OnSeek?.Invoke(Current);
}
}
@@ -3,11 +3,11 @@
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Threading;
using OpenTK;
using osu.Framework.Audio;
using osu.Framework.Allocation;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume
{
@@ -64,15 +64,25 @@ namespace osu.Game.Graphics.UserInterface.Volume
volumeMeterMusic.Bindable.ValueChanged -= volumeChanged;
}
public void Adjust(InputState state)
public bool Adjust(GlobalAction action)
{
if (State == Visibility.Hidden)
switch (action)
{
Show();
return;
case GlobalAction.DecreaseVolume:
if (State == Visibility.Hidden)
Show();
else
volumeMeterMaster.Decrease();
return true;
case GlobalAction.IncreaseVolume:
if (State == Visibility.Hidden)
Show();
else
volumeMeterMaster.Increase();
return true;
}
volumeMeterMaster.TriggerOnWheel(state);
return false;
}
[BackgroundDependencyLoader]
@@ -3,32 +3,16 @@
using System;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using OpenTK.Input;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume
{
internal class VolumeControlReceptor : Container
internal class VolumeControlReceptor : Container, IKeyBindingHandler<GlobalAction>
{
public Action<InputState> ActionRequested;
public Func<GlobalAction, bool> ActionRequested;
protected override bool OnWheel(InputState state)
{
ActionRequested?.Invoke(state);
return true;
}
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
switch (args.Key)
{
case Key.Up:
case Key.Down:
ActionRequested?.Invoke(state);
return true;
}
return base.OnKeyDown(state, args);
}
public bool OnPressed(GlobalAction action) => ActionRequested?.Invoke(action) ?? false;
public bool OnReleased(GlobalAction action) => false;
}
}
@@ -4,15 +4,16 @@
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Game.Graphics.Sprites;
using OpenTK;
using OpenTK.Graphics;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Input.Bindings;
namespace osu.Game.Graphics.UserInterface.Volume
{
internal class VolumeMeter : Container
internal class VolumeMeter : Container, IKeyBindingHandler<GlobalAction>
{
private readonly Box meterFill;
public BindableDouble Bindable { get; } = new BindableDouble();
@@ -76,12 +77,35 @@ namespace osu.Game.Graphics.UserInterface.Volume
}
}
protected override bool OnWheel(InputState state)
public void Increase()
{
Volume += 0.05f * state.Mouse.WheelDelta;
return true;
Volume += 0.05f;
}
public void Decrease()
{
Volume -= 0.05f;
}
private void updateFill() => meterFill.ScaleTo(new Vector2(1, (float)Volume), 300, Easing.OutQuint);
public bool OnPressed(GlobalAction action)
{
if (!IsHovered) return false;
switch (action)
{
case GlobalAction.DecreaseVolume:
Decrease();
return true;
case GlobalAction.IncreaseVolume:
Increase();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => false;
}
}
@@ -26,7 +26,10 @@ namespace osu.Game.Input.Bindings
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),
new KeyBinding(new[] { InputKey.Up }, GlobalAction.IncreaseVolume),
new KeyBinding(new[] { InputKey.MouseWheelUp }, GlobalAction.IncreaseVolume),
new KeyBinding(new[] { InputKey.Down }, GlobalAction.DecreaseVolume),
new KeyBinding(new[] { InputKey.MouseWheelDown }, GlobalAction.DecreaseVolume),
};
protected override IEnumerable<Drawable> KeyBindingInputQueue =>
@@ -47,5 +50,9 @@ namespace osu.Game.Input.Bindings
ToggleSettings,
[Description("Toggle osu!direct")]
ToggleDirect,
[Description("Increase Volume")]
IncreaseVolume,
[Description("Decrease Volume")]
DecreaseVolume,
}
}
@@ -2,6 +2,8 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using osu.Framework.Input;
using osu.Framework.Input.Handlers;
using osu.Framework.Platform;
using OpenTK;
@@ -29,5 +31,18 @@ namespace osu.Game.Input.Handlers
public override bool IsActive => true;
public override int Priority => 0;
public class ReplayState<T> : InputState
where T : struct
{
public List<T> PressedActions;
public override InputState Clone()
{
var clone = (ReplayState<T>)base.Clone();
clone.PressedActions = new List<T>(PressedActions);
return clone;
}
}
}
}
+1 -1
View File
@@ -22,7 +22,7 @@ namespace osu.Game.Input
{
var ruleset = info.CreateInstance();
foreach (var variant in ruleset.AvailableVariants)
insertDefaults(ruleset.GetDefaultKeyBindings(), info.ID, variant);
insertDefaults(ruleset.GetDefaultKeyBindings(variant), info.ID, variant);
}
}
+35 -8
View File
@@ -11,11 +11,11 @@ namespace osu.Game.Online.API
/// An API request with a well-defined response type.
/// </summary>
/// <typeparam name="T">Type of the response (used for deserialisation).</typeparam>
public class APIRequest<T> : APIRequest
public abstract class APIRequest<T> : APIRequest
{
protected override WebRequest CreateWebRequest() => new JsonWebRequest<T>(Uri);
public APIRequest()
protected APIRequest()
{
base.Success += onSuccess;
}
@@ -28,10 +28,36 @@ namespace osu.Game.Online.API
public new event APISuccessHandler<T> Success;
}
public abstract class APIDownloadRequest : APIRequest
{
protected override WebRequest CreateWebRequest()
{
var request = new WebRequest(Uri);
request.DownloadProgress += request_Progress;
return request;
}
private void request_Progress(WebRequest request, long current, long total) => API.Scheduler.Add(delegate { Progress?.Invoke(current, total); });
protected APIDownloadRequest()
{
base.Success += onSuccess;
}
private void onSuccess()
{
Success?.Invoke(WebRequest.ResponseData);
}
public event APIProgressHandler Progress;
public new event APISuccessHandler<byte[]> Success;
}
/// <summary>
/// AN API request with no specified response type.
/// </summary>
public class APIRequest
public abstract class APIRequest
{
/// <summary>
/// The maximum amount of time before this request will fail.
@@ -42,7 +68,7 @@ namespace osu.Game.Online.API
protected virtual WebRequest CreateWebRequest() => new WebRequest(Uri);
protected virtual string Uri => $@"{api.Endpoint}/api/v2/{Target}";
protected virtual string Uri => $@"{API.Endpoint}/api/v2/{Target}";
private double remainingTime => Math.Max(0, Timeout - (DateTime.Now.TotalMilliseconds() - (startTime ?? 0)));
@@ -52,7 +78,7 @@ namespace osu.Game.Online.API
public double StartTime => startTime ?? -1;
private APIAccess api;
protected APIAccess API;
protected WebRequest WebRequest;
public event APISuccessHandler Success;
@@ -64,7 +90,7 @@ namespace osu.Game.Online.API
public void Perform(APIAccess api)
{
this.api = api;
API = api;
if (checkAndProcessFailure())
return;
@@ -109,9 +135,9 @@ namespace osu.Game.Online.API
/// <returns>Whether we are in a failed or cancelled state.</returns>
private bool checkAndProcessFailure()
{
if (api == null || pendingFailure == null) return cancelled;
if (API == null || pendingFailure == null) return cancelled;
api.Scheduler.Add(pendingFailure);
API.Scheduler.Add(pendingFailure);
pendingFailure = null;
return true;
}
@@ -119,5 +145,6 @@ namespace osu.Game.Online.API
public delegate void APIFailureHandler(Exception e);
public delegate void APISuccessHandler();
public delegate void APIProgressHandler(long current, long total);
public delegate void APISuccessHandler<in T>(T content);
}
@@ -46,6 +46,9 @@ namespace osu.Game.Online.API.Requests
[JsonProperty(@"favourite_count")]
private int favouriteCount { get; set; }
[JsonProperty(@"id")]
private int onlineId { get; set; }
[JsonProperty(@"beatmaps")]
private IEnumerable<GetBeatmapSetsBeatmapResponse> beatmaps { get; set; }
@@ -53,6 +56,7 @@ namespace osu.Game.Online.API.Requests
{
return new BeatmapSetInfo
{
OnlineBeatmapSetID = onlineId,
Metadata = this,
OnlineInfo = new BeatmapSetOnlineInfo
{
+44 -1
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;
}
}
+12
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)
{
}
}
}
+12 -4
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();
}
+24 -5
View File
@@ -8,7 +8,6 @@ using osu.Game.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Overlays;
using osu.Framework.Input;
using osu.Framework.Logging;
using osu.Game.Graphics.UserInterface.Volume;
using osu.Framework.Allocation;
@@ -160,7 +159,7 @@ namespace osu.Game
new VolumeControlReceptor
{
RelativeSizeAxes = Axes.Both,
ActionRequested = delegate(InputState state) { volume.Adjust(state); }
ActionRequested = action => volume.Adjust(action)
},
mainContent = new Container
{
@@ -220,15 +219,28 @@ namespace osu.Game
dependencies.Cache(settings);
dependencies.Cache(social);
dependencies.Cache(direct);
dependencies.Cache(chat);
dependencies.Cache(userProfile);
dependencies.Cache(musicController);
dependencies.Cache(notificationOverlay);
dependencies.Cache(dialogOverlay);
// ensure both overlays aren't presented at the same time
chat.StateChanged += (container, state) => social.State = state == Visibility.Visible ? Visibility.Hidden : social.State;
social.StateChanged += (container, state) => chat.State = state == Visibility.Visible ? Visibility.Hidden : chat.State;
// ensure only one of these overlays are open at once.
var singleDisplayOverlays = new OverlayContainer[] { chat, social, direct };
foreach (var overlay in singleDisplayOverlays)
{
overlay.StateChanged += state =>
{
if (state == Visibility.Hidden) return;
foreach (var c in singleDisplayOverlays)
{
if (c == overlay) continue;
c.State = Visibility.Hidden;
}
};
}
LoadComponentAsync(Toolbar = new Toolbar
{
@@ -359,6 +371,13 @@ namespace osu.Game
{
base.UpdateAfterChildren();
// we only want to apply these restrictions when we are inside a screen stack.
// the use case for not applying is in visual/unit tests.
bool applyRestrictions = !currentScreen?.AllowBeatmapRulesetChange ?? false;
Ruleset.Disabled = applyRestrictions;
Beatmap.Disabled = applyRestrictions;
mainContent.Padding = new MarginPadding { Top = ToolbarOffset };
Cursor.State = currentScreen?.HasLocalCursorDisplayed == false ? Visibility.Visible : Visibility.Hidden;
+8 -8
View File
@@ -94,20 +94,22 @@ 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>();
dependencies.Cache(RulesetStore = new RulesetStore(connection));
dependencies.Cache(FileStore = new FileStore(connection, Host.Storage));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, FileStore, connection, RulesetStore, Host));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, connection, Host, BeatmapManager));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, connection, Host, BeatmapManager, RulesetStore));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(connection, RulesetStore));
dependencies.Cache(new OsuColour());
@@ -150,14 +152,10 @@ namespace osu.Game
Beatmap.ValueChanged += b =>
{
// compare to last baetmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo)
// compare to last beatmap as sometimes the two may share a track representation (optimisation, see WorkingBeatmap.TransferTo)
if (lastBeatmap?.Track != b.Track)
{
// this disposal is done to stop the audio track.
// it may not be exactly what we want for cases beatmaps are reused, as it will
// trigger a fresh load of contained resources.
lastBeatmap?.Dispose();
lastBeatmap?.Track?.Dispose();
Audio.Track.AddItem(b.Track);
}
@@ -237,6 +235,8 @@ namespace osu.Game
LocalConfig.Save();
}
connection.Dispose();
base.Dispose(isDisposing);
}
}
+73 -41
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;
}
}
}
+45 -12
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());
}
}

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