1
0
mirror of https://github.com/ppy/osu.git synced 2025-01-15 06:33:20 +08:00

Merge branch 'master' into flashlight-mod

This commit is contained in:
jorolf 2018-11-15 00:35:42 +01:00 committed by GitHub
commit 41a0f9896e
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
177 changed files with 3448 additions and 2043 deletions

6
.gitignore vendored
View File

@ -252,7 +252,11 @@ paket-files/
.fake/
# JetBrains Rider
.idea/
.idea/.idea.osu/.idea/*.xml
.idea/.idea.osu/.idea/codeStyles/*.xml
.idea/.idea.osu/.idea/dataSources/*.xml
.idea/.idea.osu/.idea/dictionaries/*.xml
.idea/.idea.osu/*.iml
*.sln.iml
# CodeRush

@ -1 +1 @@
Subproject commit 9ee64e369fe6fdafc6aed40f5a35b5f01eb82c53
Subproject commit 651e598b016b43e31ab1c1b29d5b30c92361b8d9

View File

@ -5,6 +5,7 @@ using System.Linq;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Screens.Play;
using osu.Game.Tests.Visual;
@ -37,13 +38,11 @@ namespace osu.Game.Rulesets.Catch.Tests
beatmap.HitObjects.Add(new JuiceStream
{
X = 0.5f - width / 2,
ControlPoints = new[]
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
new Vector2(width * CatchPlayfield.BASE_WIDTH, 0)
},
PathType = PathType.Linear,
Distance = width * CatchPlayfield.BASE_WIDTH,
}),
StartTime = i * 2000,
NewCombo = i % 8 == 0
});

View File

@ -34,9 +34,7 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
StartTime = obj.StartTime,
Samples = obj.Samples,
ControlPoints = curveData.ControlPoints,
PathType = curveData.PathType,
Distance = curveData.Distance,
Path = curveData.Path,
NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount,
X = (positionData?.X ?? 0) / CatchPlayfield.BASE_WIDTH,

View File

@ -10,7 +10,6 @@ using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Catch.UI;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
namespace osu.Game.Rulesets.Catch.Objects
{
@ -138,28 +137,18 @@ namespace osu.Game.Rulesets.Catch.Objects
public double Duration => EndTime - StartTime;
public double Distance
private SliderPath path;
public SliderPath Path
{
get { return Path.Distance; }
set { Path.Distance = value; }
get => path;
set => path = value;
}
public SliderPath Path { get; } = new SliderPath();
public Vector2[] ControlPoints
{
get { return Path.ControlPoints; }
set { Path.ControlPoints = value; }
}
public double Distance => Path.Distance;
public List<List<SampleInfo>> NodeSamples { get; set; } = new List<List<SampleInfo>>();
public PathType PathType
{
get { return Path.PathType; }
set { Path.PathType = value; }
}
public double? LegacyLastTickOffset { get; set; }
}
}

View File

@ -5,7 +5,6 @@ using System;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Judgements;
@ -21,14 +20,8 @@ namespace osu.Game.Rulesets.Catch.UI
internal readonly CatcherArea CatcherArea;
protected override bool UserScrollSpeedAdjustment => false;
protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Constant;
public CatchPlayfield(BeatmapDifficulty difficulty, Func<CatchHitObject, DrawableHitObject<CatchHitObject>> getVisualRepresentation)
{
Direction.Value = ScrollingDirection.Down;
Container explodingFruitContainer;
Anchor = Anchor.TopCentre;
@ -55,8 +48,6 @@ namespace osu.Game.Rulesets.Catch.UI
HitObjectContainer
}
};
VisibleTimeRange.Value = BeatmapDifficulty.DifficultyRange(difficulty.ApproachRate, 1800, 1200, 450);
}
public bool CheckIfWeCanCatch(CatchHitObject obj) => CatcherArea.AttemptCatch(obj);

View File

@ -3,6 +3,7 @@
using osu.Framework.Input;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
@ -18,9 +19,15 @@ namespace osu.Game.Rulesets.Catch.UI
{
public class CatchRulesetContainer : ScrollingRulesetContainer<CatchPlayfield, CatchHitObject>
{
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Constant;
protected override bool UserScrollSpeedAdjustment => false;
public CatchRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
Direction.Value = ScrollingDirection.Down;
TimeRange.Value = BeatmapDifficulty.DifficultyRange(beatmap.BeatmapInfo.BaseDifficulty.ApproachRate, 1800, 1200, 450);
}
public override ScoreProcessor CreateScoreProcessor() => new CatchScoreProcessor(this);

View File

@ -1,33 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Tests
{
/// <summary>
/// A container which provides a <see cref="IScrollingInfo"/> to children.
/// </summary>
public class ScrollingTestContainer : Container
{
[Cached(Type = typeof(IScrollingInfo))]
private readonly TestScrollingInfo scrollingInfo = new TestScrollingInfo();
public ScrollingTestContainer(ScrollingDirection direction)
{
scrollingInfo.Direction.Value = direction;
}
public void Flip() => scrollingInfo.Direction.Value = scrollingInfo.Direction.Value == ScrollingDirection.Up ? ScrollingDirection.Down : ScrollingDirection.Up;
}
public class TestScrollingInfo : IScrollingInfo
{
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
}
}

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mania.UI.Components;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
@ -93,7 +94,6 @@ namespace osu.Game.Rulesets.Mania.Tests
Height = 0.85f,
AccentColour = Color4.OrangeRed,
Action = { Value = action },
VisibleTimeRange = { Value = 2000 }
};
columns.Add(column);
@ -104,6 +104,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Origin = Anchor.Centre,
AutoSizeAxes = Axes.X,
RelativeSizeAxes = Axes.Y,
TimeRange = 2000,
Child = column
};
}

View File

@ -0,0 +1,57 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestCaseHoldNoteSelectionBlueprint : SelectionBlueprintTestCase
{
private readonly DrawableHoldNote drawableObject;
protected override Container<Drawable> Content => content ?? base.Content;
private readonly Container content;
public TestCaseHoldNoteSelectionBlueprint()
{
var holdNote = new HoldNote { Column = 0, Duration = 1000 };
holdNote.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
AutoSizeAxes = Axes.Y,
Width = 50,
Child = drawableObject = new DrawableHoldNote(holdNote)
{
Height = 300,
AccentColour = OsuColour.Gray(0.3f)
}
};
}
protected override void Update()
{
base.Update();
foreach (var nested in drawableObject.NestedHitObjects)
{
double finalPosition = (nested.HitObject.StartTime - drawableObject.HitObject.StartTime) / drawableObject.HitObject.Duration;
nested.Y = (float)(-finalPosition * content.DrawHeight);
}
}
protected override SelectionBlueprint CreateBlueprint() => new HoldNoteSelectionBlueprint(drawableObject);
}
}

View File

@ -0,0 +1,41 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Tests
{
public class TestCaseNoteSelectionBlueprint : SelectionBlueprintTestCase
{
private readonly DrawableNote drawableObject;
protected override Container<Drawable> Content => content ?? base.Content;
private readonly Container content;
public TestCaseNoteSelectionBlueprint()
{
var note = new Note { Column = 0 };
note.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty());
base.Content.Child = content = new ScrollingTestContainer(ScrollingDirection.Down)
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(50, 20),
Child = drawableObject = new DrawableNote(note)
};
}
protected override SelectionBlueprint CreateBlueprint() => new NoteSelectionBlueprint(drawableObject);
}
}

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using osu.Game.Tests.Visual;
using OpenTK;
namespace osu.Game.Rulesets.Mania.Tests
@ -122,7 +123,7 @@ namespace osu.Game.Rulesets.Mania.Tests
{
var specialAction = ManiaAction.Special1;
var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction) { VisibleTimeRange = { Value = 2000 } };
var stage = new ManiaStage(0, new StageDefinition { Columns = 2 }, ref action, ref specialAction);
stages.Add(stage);
return new ScrollingTestContainer(direction)
@ -131,6 +132,7 @@ namespace osu.Game.Rulesets.Mania.Tests
Origin = Anchor.Centre,
RelativeSizeAxes = Axes.Y,
AutoSizeAxes = Axes.X,
TimeRange = 2000,
Child = stage
};
}

View File

@ -0,0 +1,29 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints.Components
{
public class EditNotePiece : CompositeDrawable
{
public EditNotePiece()
{
Height = NotePiece.NOTE_HEIGHT;
CornerRadius = 5;
Masking = true;
InternalChild = new NotePiece();
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Yellow;
}
}
}

View File

@ -4,18 +4,17 @@
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Primitives;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Mania.Edit.Masks
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class HoldNoteSelectionMask : SelectionMask
public class HoldNoteSelectionBlueprint : ManiaSelectionBlueprint
{
public new DrawableHoldNote HitObject => (DrawableHoldNote)base.HitObject;
@ -23,13 +22,13 @@ namespace osu.Game.Rulesets.Mania.Edit.Masks
private readonly BodyPiece body;
public HoldNoteSelectionMask(DrawableHoldNote hold)
public HoldNoteSelectionBlueprint(DrawableHoldNote hold)
: base(hold)
{
InternalChildren = new Drawable[]
{
new HoldNoteNoteSelectionMask(hold.Head),
new HoldNoteNoteSelectionMask(hold.Tail),
new HoldNoteNoteSelectionBlueprint(hold.Head),
new HoldNoteNoteSelectionBlueprint(hold.Tail),
body = new BodyPiece
{
AccentColour = Color4.Transparent
@ -59,9 +58,11 @@ namespace osu.Game.Rulesets.Mania.Edit.Masks
Y -= HitObject.Tail.DrawHeight;
}
private class HoldNoteNoteSelectionMask : NoteSelectionMask
public override Quad SelectionQuad => ScreenSpaceDrawQuad;
private class HoldNoteNoteSelectionBlueprint : NoteSelectionBlueprint
{
public HoldNoteNoteSelectionMask(DrawableNote note)
public HoldNoteNoteSelectionBlueprint(DrawableNote note)
: base(note)
{
Select();

View File

@ -0,0 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class ManiaSelectionBlueprint : SelectionBlueprint
{
public ManiaSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
{
RelativeSizeAxes = Axes.None;
}
public override void AdjustPosition(DragEvent dragEvent)
{
}
}
}

View File

@ -0,0 +1,26 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.Edit.Blueprints.Components;
using osu.Game.Rulesets.Mania.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Edit.Blueprints
{
public class NoteSelectionBlueprint : ManiaSelectionBlueprint
{
public NoteSelectionBlueprint(DrawableNote note)
: base(note)
{
AddInternal(new EditNotePiece { RelativeSizeAxes = Axes.X });
}
protected override void Update()
{
base.Update();
Size = HitObject.DrawSize;
Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft);
}
}
}

View File

@ -6,11 +6,14 @@ using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaEditRulesetContainer : ManiaRulesetContainer
{
public new IScrollingInfo ScrollingInfo => base.ScrollingInfo;
public ManiaEditRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{

View File

@ -10,45 +10,46 @@ using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Objects.Drawables;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Edit.Masks;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Mania.Edit.Blueprints;
using osu.Game.Rulesets.UI;
namespace osu.Game.Rulesets.Mania.Edit
{
public class ManiaHitObjectComposer : HitObjectComposer<ManiaHitObject>
{
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
public ManiaHitObjectComposer(Ruleset ruleset)
: base(ruleset)
{
}
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
dependencies.CacheAs<IScrollingInfo>(new ManiaScrollingInfo(Config));
return dependencies;
}
=> dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
protected override RulesetContainer<ManiaHitObject> CreateRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
=> new ManiaEditRulesetContainer(ruleset, beatmap);
{
var rulesetContainer = new ManiaEditRulesetContainer(ruleset, beatmap);
// This is the earliest we can cache the scrolling info to ourselves, before masks are added to the hierarchy and inject it
dependencies.CacheAs(rulesetContainer.ScrollingInfo);
return rulesetContainer;
}
protected override IReadOnlyList<HitObjectCompositionTool> CompositionTools => Array.Empty<HitObjectCompositionTool>();
public override SelectionMask CreateMaskFor(DrawableHitObject hitObject)
public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableNote note:
return new NoteSelectionMask(note);
return new NoteSelectionBlueprint(note);
case DrawableHoldNote holdNote:
return new HoldNoteSelectionMask(holdNote);
return new HoldNoteSelectionBlueprint(holdNote);
}
return base.CreateMaskFor(hitObject);
return base.CreateBlueprintFor(hitObject);
}
}
}

View File

@ -0,0 +1,18 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Edit.Masks
{
public abstract class ManiaSelectionBlueprint : SelectionBlueprint
{
protected ManiaSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
{
RelativeSizeAxes = Axes.None;
}
}
}

View File

@ -1,39 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Mania.Objects.Drawables;
using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
namespace osu.Game.Rulesets.Mania.Edit.Masks
{
public class NoteSelectionMask : SelectionMask
{
public NoteSelectionMask(DrawableNote note)
: base(note)
{
Scale = note.Scale;
CornerRadius = 5;
Masking = true;
AddInternal(new NotePiece());
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.Yellow;
}
protected override void Update()
{
base.Update();
Size = HitObject.DrawSize;
Position = Parent.ToLocalSpace(HitObject.ScreenSpaceDrawQuad.TopLeft);
}
}
}

View File

@ -5,7 +5,6 @@ using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;

View File

@ -9,7 +9,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Graphics;
using osu.Game.Rulesets.Mania.UI;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.Objects.Drawables.Pieces

View File

@ -1,12 +1,12 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using OpenTK.Graphics;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Input.Bindings;
@ -16,7 +16,7 @@ using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public class Column : ManiaScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
public class Column : ScrollingPlayfield, IKeyBindingHandler<ManiaAction>, IHasAccentColour
{
private const float column_width = 45;
private const float special_column_width = 70;
@ -134,7 +134,7 @@ namespace osu.Game.Rulesets.Mania.UI
hitObject.AccentColour = AccentColour;
hitObject.OnNewResult += OnNewResult;
HitObjects.Add(hitObject);
HitObjectContainer.Add(hitObject);
}
internal void OnNewResult(DrawableHitObject judgedObject, JudgementResult result)
@ -144,7 +144,7 @@ namespace osu.Game.Rulesets.Mania.UI
explosionContainer.Add(new HitExplosion(judgedObject)
{
Anchor = Direction == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
Anchor = Direction.Value == ScrollingDirection.Up ? Anchor.TopCentre : Anchor.BottomCentre
});
}
@ -154,10 +154,10 @@ namespace osu.Game.Rulesets.Mania.UI
return false;
var nextObject =
HitObjects.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
HitObjectContainer.AliveObjects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
// fallback to non-alive objects to find next off-screen object
HitObjects.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
HitObjects.Objects.LastOrDefault();
HitObjectContainer.Objects.FirstOrDefault(h => h.HitObject.StartTime > Time.Current) ??
HitObjectContainer.Objects.LastOrDefault();
nextObject?.PlaySamples();

View File

@ -1,20 +1,19 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using System;
using System.Collections.Generic;
using osu.Game.Rulesets.Mania.Beatmaps;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.Mania.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI.Scrolling;
using OpenTK;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaPlayfield : ManiaScrollingPlayfield
public class ManiaPlayfield : ScrollingPlayfield
{
private readonly List<ManiaStage> stages = new List<ManiaStage>();
@ -41,7 +40,6 @@ namespace osu.Game.Rulesets.Mania.UI
for (int i = 0; i < stageDefinitions.Count; i++)
{
var newStage = new ManiaStage(firstColumnIndex, stageDefinitions[i], ref normalColumnAction, ref specialColumnAction);
newStage.VisibleTimeRange.BindTo(VisibleTimeRange);
playfieldGrid.Content[0][i] = newStage;
@ -68,11 +66,5 @@ namespace osu.Game.Rulesets.Mania.UI
return null;
}
[BackgroundDependencyLoader]
private void load(ManiaConfigManager maniaConfig)
{
maniaConfig.BindWith(ManiaSetting.ScrollTime, VisibleTimeRange);
}
}
}

View File

@ -4,6 +4,7 @@
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input;
@ -35,6 +36,8 @@ namespace osu.Game.Rulesets.Mania.UI
protected new ManiaConfigManager Config => (ManiaConfigManager)base.Config;
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
public ManiaRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
@ -70,18 +73,11 @@ namespace osu.Game.Rulesets.Mania.UI
private void load()
{
BarLines.ForEach(Playfield.Add);
}
private DependencyContainer dependencies;
Config.BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true);
protected override IReadOnlyDependencyContainer CreateChildDependencies(IReadOnlyDependencyContainer parent)
{
dependencies = new DependencyContainer(base.CreateChildDependencies(parent));
if (dependencies.Get<ManiaScrollingInfo>() == null)
dependencies.CacheAs<IScrollingInfo>(new ManiaScrollingInfo(Config));
return dependencies;
Config.BindWith(ManiaSetting.ScrollTime, TimeRange);
}
protected override Playfield CreatePlayfield() => new ManiaPlayfield(Beatmap.Stages)

View File

@ -1,23 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Configuration;
using osu.Game.Rulesets.Mania.Configuration;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public class ManiaScrollingInfo : IScrollingInfo
{
private readonly Bindable<ManiaScrollingDirection> configDirection = new Bindable<ManiaScrollingDirection>();
public readonly Bindable<ScrollingDirection> Direction = new Bindable<ScrollingDirection>();
IBindable<ScrollingDirection> IScrollingInfo.Direction => Direction;
public ManiaScrollingInfo(ManiaConfigManager config)
{
config.BindWith(ManiaSetting.ScrollDirection, configDirection);
configDirection.BindValueChanged(v => Direction.Value = (ScrollingDirection)v, true);
}
}
}

View File

@ -1,21 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Game.Rulesets.UI.Scrolling;
namespace osu.Game.Rulesets.Mania.UI
{
public abstract class ManiaScrollingPlayfield : ScrollingPlayfield
{
private readonly IBindable<ScrollingDirection> direction = new Bindable<ScrollingDirection>();
[BackgroundDependencyLoader]
private void load(IScrollingInfo scrollingInfo)
{
direction.BindTo(scrollingInfo.Direction);
direction.BindValueChanged(direction => Direction.Value = direction, true);
}
}
}

View File

@ -23,7 +23,7 @@ namespace osu.Game.Rulesets.Mania.UI
/// <summary>
/// A collection of <see cref="Column"/>s.
/// </summary>
public class ManiaStage : ManiaScrollingPlayfield
public class ManiaStage : ScrollingPlayfield
{
public const float HIT_TARGET_POSITION = 50;
@ -144,8 +144,6 @@ namespace osu.Game.Rulesets.Mania.UI
public void AddColumn(Column c)
{
c.VisibleTimeRange.BindTo(VisibleTimeRange);
topLevelContainer.Add(c.TopLevelContainer.CreateProxy());
columnFlow.Add(c);
AddNested(c);

View File

@ -4,16 +4,16 @@
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseHitCirclePlacementMask : HitObjectPlacementMaskTestCase
public class TestCaseHitCirclePlacementBlueprint : PlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableHitCircle((HitCircle)hitObject);
protected override PlacementMask CreateMask() => new HitCirclePlacementMask();
protected override PlacementBlueprint CreateBlueprint() => new HitCirclePlacementBlueprint();
}
}

View File

@ -4,7 +4,7 @@
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
@ -12,11 +12,11 @@ using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseHitCircleSelectionMask : HitObjectSelectionMaskTestCase
public class TestCaseHitCircleSelectionBlueprint : SelectionBlueprintTestCase
{
private readonly DrawableHitCircle drawableObject;
public TestCaseHitCircleSelectionMask()
public TestCaseHitCircleSelectionBlueprint()
{
var hitCircle = new HitCircle { Position = new Vector2(256, 192) };
hitCircle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
@ -24,6 +24,6 @@ namespace osu.Game.Rulesets.Osu.Tests
Add(drawableObject = new DrawableHitCircle(hitCircle));
}
protected override SelectionMask CreateMask() => new HitCircleSelectionMask(drawableObject);
protected override SelectionBlueprint CreateBlueprint() => new HitCircleSelectionBlueprint(drawableObject);
}
}

View File

@ -18,6 +18,7 @@ using System.Linq;
using NUnit.Framework;
using osu.Game.Graphics.Sprites;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
@ -108,13 +109,12 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(239, 176),
ControlPoints = new[]
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,
new Vector2(154, 28),
new Vector2(52, -34)
},
Distance = 700,
}, 700),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats),
StackHeight = 10
@ -141,12 +141,11 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0),
ControlPoints = new[]
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,
new Vector2(distance, 0),
},
Distance = distance,
}, distance),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats),
StackHeight = stackHeight
@ -161,13 +160,12 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ControlPoints = new[]
Path = new SliderPath(PathType.PerfectCurve, new[]
{
Vector2.Zero,
new Vector2(200, 200),
new Vector2(400, 0)
},
Distance = 600,
}, 600),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats)
};
@ -181,10 +179,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
PathType = PathType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ControlPoints = new[]
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
new Vector2(150, 75),
@ -192,8 +189,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(300, -200),
new Vector2(400, 0),
new Vector2(430, 0)
},
Distance = 793.4417,
}),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats)
};
@ -207,18 +203,16 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
PathType = PathType.Bezier,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ControlPoints = new[]
Path = new SliderPath(PathType.Bezier, new[]
{
Vector2.Zero,
new Vector2(150, 75),
new Vector2(200, 100),
new Vector2(300, -200),
new Vector2(430, 0)
},
Distance = 480,
}),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats)
};
@ -232,10 +226,9 @@ namespace osu.Game.Rulesets.Osu.Tests
{
var slider = new Slider
{
PathType = PathType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(0, 0),
ControlPoints = new[]
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
new Vector2(-200, 0),
@ -243,8 +236,7 @@ namespace osu.Game.Rulesets.Osu.Tests
new Vector2(0, -200),
new Vector2(-200, -200),
new Vector2(0, -200)
},
Distance = 1000,
}),
RepeatCount = repeats,
NodeSamples = createEmptySamples(repeats)
};
@ -264,15 +256,13 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0),
PathType = PathType.Catmull,
ControlPoints = new[]
Path = new SliderPath(PathType.Catmull, new[]
{
Vector2.Zero,
new Vector2(50, -50),
new Vector2(150, 50),
new Vector2(200, 0)
},
Distance = 300,
}),
RepeatCount = repeats,
NodeSamples = repeatSamples
};

View File

@ -4,16 +4,16 @@
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseSliderPlacementMask : HitObjectPlacementMaskTestCase
public class TestCaseSliderPlacementBlueprint : PlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSlider((Slider)hitObject);
protected override PlacementMask CreateMask() => new SliderPlacementMask();
protected override PlacementBlueprint CreateBlueprint() => new SliderPlacementBlueprint();
}
}

View File

@ -6,9 +6,10 @@ using System.Collections.Generic;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
@ -16,12 +17,12 @@ using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseSliderSelectionMask : HitObjectSelectionMaskTestCase
public class TestCaseSliderSelectionBlueprint : SelectionBlueprintTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SliderSelectionMask),
typeof(SliderCircleSelectionMask),
typeof(SliderSelectionBlueprint),
typeof(SliderCircleSelectionBlueprint),
typeof(SliderBodyPiece),
typeof(SliderCircle),
typeof(PathControlPointVisualiser),
@ -30,19 +31,17 @@ namespace osu.Game.Rulesets.Osu.Tests
private readonly DrawableSlider drawableObject;
public TestCaseSliderSelectionMask()
public TestCaseSliderSelectionBlueprint()
{
var slider = new Slider
{
Position = new Vector2(256, 192),
ControlPoints = new[]
Path = new SliderPath(PathType.Bezier, new[]
{
Vector2.Zero,
new Vector2(150, 150),
new Vector2(300, 0)
},
PathType = PathType.Bezier,
Distance = 350
})
};
slider.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = 2 });
@ -50,6 +49,6 @@ namespace osu.Game.Rulesets.Osu.Tests
Add(drawableObject = new DrawableSlider(slider));
}
protected override SelectionMask CreateMask() => new SliderSelectionMask(drawableObject);
protected override SelectionBlueprint CreateBlueprint() => new SliderSelectionBlueprint(drawableObject);
}
}

View File

@ -4,17 +4,17 @@
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseSpinnerPlacementMask : HitObjectPlacementMaskTestCase
public class TestCaseSpinnerPlacementBlueprint : PlacementBlueprintTestCase
{
protected override DrawableHitObject CreateHitObject(HitObject hitObject) => new DrawableSpinner((Spinner)hitObject);
protected override PlacementMask CreateMask() => new SpinnerPlacementMask();
protected override PlacementBlueprint CreateBlueprint() => new SpinnerPlacementBlueprint();
}
}

View File

@ -8,8 +8,8 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks;
using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
@ -17,17 +17,17 @@ using OpenTK;
namespace osu.Game.Rulesets.Osu.Tests
{
public class TestCaseSpinnerSelectionMask : HitObjectSelectionMaskTestCase
public class TestCaseSpinnerSelectionBlueprint : SelectionBlueprintTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(SpinnerSelectionMask),
typeof(SpinnerSelectionBlueprint),
typeof(SpinnerPiece)
};
private readonly DrawableSpinner drawableSpinner;
public TestCaseSpinnerSelectionMask()
public TestCaseSpinnerSelectionBlueprint()
{
var spinner = new Spinner
{
@ -45,6 +45,6 @@ namespace osu.Game.Rulesets.Osu.Tests
});
}
protected override SelectionMask CreateMask() => new SpinnerSelectionMask(drawableSpinner) { Size = new Vector2(0.5f) };
protected override SelectionBlueprint CreateBlueprint() => new SpinnerSelectionBlueprint(drawableSpinner) { Size = new Vector2(0.5f) };
}
}

View File

@ -35,9 +35,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
{
StartTime = original.StartTime,
Samples = original.Samples,
ControlPoints = curveData.ControlPoints,
PathType = curveData.PathType,
Distance = curveData.Distance,
Path = curveData.Path,
NodeSamples = curveData.NodeSamples,
RepeatCount = curveData.RepeatCount,
Position = positionData?.Position ?? Vector2.Zero,

View File

@ -9,7 +9,7 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks.Components
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components
{
public class HitCirclePiece : CompositeDrawable
{

View File

@ -3,16 +3,16 @@
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public class HitCirclePlacementMask : PlacementMask
public class HitCirclePlacementBlueprint : PlacementBlueprint
{
public new HitCircle HitObject => (HitCircle)base.HitObject;
public HitCirclePlacementMask()
public HitCirclePlacementBlueprint()
: base(new HitCircle())
{
InternalChild = new HitCirclePiece(HitObject);

View File

@ -1,16 +1,15 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles
{
public class HitCircleSelectionMask : SelectionMask
public class HitCircleSelectionBlueprint : OsuSelectionBlueprint
{
public HitCircleSelectionMask(DrawableHitCircle hitCircle)
public HitCircleSelectionBlueprint(DrawableHitCircle hitCircle)
: base(hitCircle)
{
InternalChild = new HitCirclePiece((HitCircle)hitCircle.HitObject);

View File

@ -0,0 +1,22 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints
{
public class OsuSelectionBlueprint : SelectionBlueprint
{
protected OsuHitObject OsuObject => (OsuHitObject)HitObject.HitObject;
public OsuSelectionBlueprint(DrawableHitObject hitObject)
: base(hitObject)
{
}
public override void AdjustPosition(DragEvent dragEvent) => OsuObject.Position += dragEvent.Delta;
}
}

View File

@ -1,7 +1,6 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@ -9,10 +8,11 @@ using osu.Framework.Graphics.Lines;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Events;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Osu.Objects;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointPiece : CompositeDrawable
{
@ -55,16 +55,16 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
{
base.Update();
Position = slider.StackedPosition + slider.ControlPoints[index];
Position = slider.StackedPosition + slider.Path.ControlPoints[index];
marker.Colour = isSegmentSeparator ? colours.Red : colours.Yellow;
path.ClearVertices();
if (index != slider.ControlPoints.Length - 1)
if (index != slider.Path.ControlPoints.Length - 1)
{
path.AddVertex(Vector2.Zero);
path.AddVertex(slider.ControlPoints[index + 1] - slider.ControlPoints[index]);
path.AddVertex(slider.Path.ControlPoints[index + 1] - slider.Path.ControlPoints[index]);
}
path.OriginPosition = path.PositionInBoundingBox(Vector2.Zero);
@ -76,7 +76,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
protected override bool OnDrag(DragEvent e)
{
var newControlPoints = slider.ControlPoints.ToArray();
var newControlPoints = slider.Path.ControlPoints.ToArray();
if (index == 0)
{
@ -96,8 +96,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
if (isSegmentSeparatorWithPrevious)
newControlPoints[index - 1] = newControlPoints[index];
slider.ControlPoints = newControlPoints;
slider.Path.Calculate(true);
slider.Path = new SliderPath(slider.Path.Type, newControlPoints);
return true;
}
@ -106,8 +105,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
private bool isSegmentSeparator => isSegmentSeparatorWithNext || isSegmentSeparatorWithPrevious;
private bool isSegmentSeparatorWithNext => index < slider.ControlPoints.Length - 1 && slider.ControlPoints[index + 1] == slider.ControlPoints[index];
private bool isSegmentSeparatorWithNext => index < slider.Path.ControlPoints.Length - 1 && slider.Path.ControlPoints[index + 1] == slider.Path.ControlPoints[index];
private bool isSegmentSeparatorWithPrevious => index > 0 && slider.ControlPoints[index - 1] == slider.ControlPoints[index];
private bool isSegmentSeparatorWithPrevious => index > 0 && slider.Path.ControlPoints[index - 1] == slider.Path.ControlPoints[index];
}
}

View File

@ -5,7 +5,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class PathControlPointVisualiser : CompositeDrawable
{
@ -19,15 +19,15 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
InternalChild = pieces = new Container<PathControlPointPiece> { RelativeSizeAxes = Axes.Both };
slider.ControlPointsChanged += _ => updatePathControlPoints();
slider.PathChanged += _ => updatePathControlPoints();
updatePathControlPoints();
}
private void updatePathControlPoints()
{
while (slider.ControlPoints.Length > pieces.Count)
while (slider.Path.ControlPoints.Length > pieces.Count)
pieces.Add(new PathControlPointPiece(slider, pieces.Count));
while (slider.ControlPoints.Length < pieces.Count)
while (slider.Path.ControlPoints.Length < pieces.Count)
pieces.Remove(pieces[pieces.Count - 1]);
}
}

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class SliderBodyPiece : CompositeDrawable
{
@ -45,8 +45,6 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
{
base.Update();
slider.Path.Calculate();
var vertices = new List<Vector2>();
slider.Path.GetPathToProgress(vertices, 0, 1);

View File

@ -1,10 +1,10 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components
{
public class SliderCirclePiece : HitCirclePiece
{
@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components
this.slider = slider;
this.position = position;
slider.ControlPointsChanged += _ => UpdatePosition();
slider.PathChanged += _ => UpdatePosition();
}
protected override void UpdatePosition()

View File

@ -1,18 +1,22 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderCircleSelectionMask : SelectionMask
public class SliderCircleSelectionBlueprint : OsuSelectionBlueprint
{
public SliderCircleSelectionMask(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position)
private readonly Slider slider;
public SliderCircleSelectionBlueprint(DrawableOsuHitObject hitObject, Slider slider, SliderPosition position)
: base(hitObject)
{
this.slider = slider;
InternalChild = new SliderCirclePiece(slider, position);
Select();
@ -20,5 +24,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
// Todo: This is temporary, since the slider circle masks don't do anything special yet. In the future they will handle input.
public override bool HandlePositionalInput => false;
public override void AdjustPosition(DragEvent dragEvent) => slider.Position += dragEvent.Delta;
}
}

View File

@ -1,24 +1,23 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using OpenTK;
using OpenTK.Input;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderPlacementMask : PlacementMask
public class SliderPlacementBlueprint : PlacementBlueprint
{
public new Objects.Slider HitObject => (Objects.Slider)base.HitObject;
@ -27,7 +26,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
private PlacementState state;
public SliderPlacementMask()
public SliderPlacementBlueprint()
: base(new Objects.Slider())
{
RelativeSizeAxes = Axes.Both;
@ -119,12 +118,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
private void updateSlider()
{
for (int i = 0; i < segments.Count; i++)
segments[i].Calculate(i == segments.Count - 1 ? (Vector2?)cursor : null);
HitObject.ControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
HitObject.PathType = HitObject.ControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear;
HitObject.Distance = segments.Sum(s => s.Distance);
var newControlPoints = segments.SelectMany(s => s.ControlPoints).Concat(cursor.Yield()).ToArray();
HitObject.Path = new SliderPath(newControlPoints.Length > 2 ? PathType.Bezier : PathType.Linear, newControlPoints);
}
private void setState(PlacementState newState)
@ -140,41 +135,12 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
private class Segment
{
public float Distance { get; private set; }
public readonly List<Vector2> ControlPoints = new List<Vector2>();
public Segment(Vector2 offset)
{
ControlPoints.Add(offset);
}
public void Calculate(Vector2? cursor = null)
{
Span<Vector2> allControlPoints = stackalloc Vector2[ControlPoints.Count + (cursor.HasValue ? 1 : 0)];
for (int i = 0; i < ControlPoints.Count; i++)
allControlPoints[i] = ControlPoints[i];
if (cursor.HasValue)
allControlPoints[allControlPoints.Length - 1] = cursor.Value;
List<Vector2> result;
switch (allControlPoints.Length)
{
case 1:
case 2:
result = PathApproximator.ApproximateLinear(allControlPoints);
break;
default:
result = PathApproximator.ApproximateBezier(allControlPoints);
break;
}
Distance = 0;
for (int i = 0; i < result.Count - 1; i++)
Distance += Vector2.Distance(result[i], result[i + 1]);
}
}
}
}

View File

@ -1,7 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public enum SliderPosition
{

View File

@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders
{
public class SliderSelectionBlueprint : OsuSelectionBlueprint
{
private readonly SliderCircleSelectionBlueprint headBlueprint;
public SliderSelectionBlueprint(DrawableSlider slider)
: base(slider)
{
var sliderObject = (Slider)slider.HitObject;
InternalChildren = new Drawable[]
{
new SliderBodyPiece(sliderObject),
headBlueprint = new SliderCircleSelectionBlueprint(slider.HeadCircle, sliderObject, SliderPosition.Start),
new SliderCircleSelectionBlueprint(slider.TailCircle, sliderObject, SliderPosition.End),
new PathControlPointVisualiser(sliderObject),
};
}
public override Vector2 SelectionPoint => headBlueprint.SelectionPoint;
}
}

View File

@ -10,7 +10,7 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks.Components
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components
{
public class SpinnerPiece : CompositeDrawable
{

View File

@ -4,13 +4,13 @@
using osu.Framework.Graphics;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.UI;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
public class SpinnerPlacementMask : PlacementMask
public class SpinnerPlacementBlueprint : PlacementBlueprint
{
public new Spinner HitObject => (Spinner)base.HitObject;
@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks
private bool isPlacingEnd;
public SpinnerPlacementMask()
public SpinnerPlacementBlueprint()
: base(new Spinner { Position = OsuPlayfield.BASE_SIZE / 2 })
{
InternalChild = piece = new SpinnerPiece(HitObject) { Alpha = 0.5f };
@ -37,6 +37,8 @@ namespace osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks
isPlacingEnd = true;
piece.FadeTo(1f, 150, Easing.OutQuint);
BeginPlacement();
}
return true;

View File

@ -1,24 +1,29 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks.Components;
using osu.Framework.Input.Events;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks
namespace osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners
{
public class SpinnerSelectionMask : SelectionMask
public class SpinnerSelectionBlueprint : OsuSelectionBlueprint
{
private readonly SpinnerPiece piece;
public SpinnerSelectionMask(DrawableSpinner spinner)
public SpinnerSelectionBlueprint(DrawableSpinner spinner)
: base(spinner)
{
InternalChild = piece = new SpinnerPiece((Spinner)spinner.HitObject);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => piece.ReceivePositionalInputAt(screenSpacePos);
public override void AdjustPosition(DragEvent dragEvent)
{
// Spinners don't support position adjustments
}
}
}

View File

@ -3,7 +3,7 @@
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
public override PlacementMask CreatePlacementMask() => new HitCirclePlacementMask();
public override PlacementBlueprint CreatePlacementBlueprint() => new HitCirclePlacementBlueprint();
}
}

View File

@ -1,33 +0,0 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks
{
public class SliderSelectionMask : SelectionMask
{
private readonly SliderCircleSelectionMask headMask;
public SliderSelectionMask(DrawableSlider slider)
: base(slider)
{
var sliderObject = (Slider)slider.HitObject;
InternalChildren = new Drawable[]
{
new SliderBodyPiece(sliderObject),
headMask = new SliderCircleSelectionMask(slider.HeadCircle, sliderObject, SliderPosition.Start),
new SliderCircleSelectionMask(slider.TailCircle, sliderObject, SliderPosition.End),
new PathControlPointVisualiser(sliderObject),
};
}
public override Vector2 SelectionPoint => headMask.SelectionPoint;
}
}

View File

@ -8,9 +8,9 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks;
using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Rulesets.Osu.UI;
@ -37,19 +37,19 @@ namespace osu.Game.Rulesets.Osu.Edit
protected override Container CreateLayerContainer() => new PlayfieldAdjustmentContainer { RelativeSizeAxes = Axes.Both };
public override SelectionMask CreateMaskFor(DrawableHitObject hitObject)
public override SelectionBlueprint CreateBlueprintFor(DrawableHitObject hitObject)
{
switch (hitObject)
{
case DrawableHitCircle circle:
return new HitCircleSelectionMask(circle);
return new HitCircleSelectionBlueprint(circle);
case DrawableSlider slider:
return new SliderSelectionMask(slider);
return new SliderSelectionBlueprint(slider);
case DrawableSpinner spinner:
return new SpinnerSelectionMask(spinner);
return new SpinnerSelectionBlueprint(spinner);
}
return base.CreateMaskFor(hitObject);
return base.CreateBlueprintFor(hitObject);
}
}
}

View File

@ -3,7 +3,7 @@
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Edit.Masks.SliderMasks;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Sliders;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
public override PlacementMask CreatePlacementMask() => new SliderPlacementMask();
public override PlacementBlueprint CreatePlacementBlueprint() => new SliderPlacementBlueprint();
}
}

View File

@ -3,7 +3,7 @@
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu.Edit.Masks.SpinnerMasks;
using osu.Game.Rulesets.Osu.Edit.Blueprints.Spinners;
using osu.Game.Rulesets.Osu.Objects;
namespace osu.Game.Rulesets.Osu.Edit
@ -15,6 +15,6 @@ namespace osu.Game.Rulesets.Osu.Edit
{
}
public override PlacementMask CreatePlacementMask() => new SpinnerPlacementMask();
public override PlacementBlueprint CreatePlacementBlueprint() => new SpinnerPlacementBlueprint();
}
}

View File

@ -32,12 +32,11 @@ namespace osu.Game.Rulesets.Osu.Mods
slider.NestedHitObjects.OfType<SliderTick>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
slider.NestedHitObjects.OfType<RepeatPoint>().ForEach(h => h.Position = new Vector2(h.Position.X, OsuPlayfield.BASE_SIZE.Y - h.Position.Y));
var newControlPoints = new Vector2[slider.ControlPoints.Length];
for (int i = 0; i < slider.ControlPoints.Length; i++)
newControlPoints[i] = new Vector2(slider.ControlPoints[i].X, -slider.ControlPoints[i].Y);
var newControlPoints = new Vector2[slider.Path.ControlPoints.Length];
for (int i = 0; i < slider.Path.ControlPoints.Length; i++)
newControlPoints[i] = new Vector2(slider.Path.ControlPoints[i].X, -slider.Path.ControlPoints[i].Y);
slider.ControlPoints = newControlPoints;
slider.Path?.Calculate(); // Recalculate the slider curve
slider.Path = new SliderPath(slider.Path.Type, newControlPoints, slider.Path.ExpectedDistance);
}
}
}

View File

@ -91,7 +91,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
Ball.Scale = new Vector2(HitObject.Scale);
};
slider.ControlPointsChanged += _ => Body.Refresh();
slider.PathChanged += _ => Body.Refresh();
}
public override Color4 AccentColour

View File

@ -17,7 +17,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
this.slider = slider;
h.PositionChanged += _ => updatePosition();
slider.ControlPointsChanged += _ => updatePosition();
slider.PathChanged += _ => updatePosition();
updatePosition();
}

View File

@ -30,7 +30,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
AlwaysPresent = true;
hitCircle.PositionChanged += _ => updatePosition();
slider.ControlPointsChanged += _ => updatePosition();
slider.PathChanged += _ => updatePosition();
updatePosition();
}

View File

@ -7,11 +7,10 @@ using osu.Game.Rulesets.Objects;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit.Types;
namespace osu.Game.Rulesets.Osu.Objects
{
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasEditablePosition
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
{
public const double OBJECT_RADIUS = 64;
@ -100,8 +99,6 @@ namespace osu.Game.Rulesets.Osu.Objects
Scale = (1.0f - 0.7f * (difficulty.CircleSize - 5) / 5) / 2;
}
public virtual void OffsetPosition(Vector2 offset) => Position += offset;
protected override HitWindows CreateHitWindows() => new OsuHitWindows();
}
}

View File

@ -22,7 +22,7 @@ namespace osu.Game.Rulesets.Osu.Objects
/// </summary>
private const float base_scoring_distance = 100;
public event Action<Vector2[]> ControlPointsChanged;
public event Action<SliderPath> PathChanged;
public double EndTime => StartTime + this.SpanCount() * Path.Distance / Velocity;
public double Duration => EndTime - StartTime;
@ -52,35 +52,23 @@ namespace osu.Game.Rulesets.Osu.Objects
}
}
public SliderPath Path { get; } = new SliderPath();
private SliderPath path;
public Vector2[] ControlPoints
public SliderPath Path
{
get => Path.ControlPoints;
get => path;
set
{
if (Path.ControlPoints == value)
return;
Path.ControlPoints = value;
path = value;
ControlPointsChanged?.Invoke(value);
PathChanged?.Invoke(value);
if (TailCircle != null)
TailCircle.Position = EndPosition;
}
}
public PathType PathType
{
get { return Path.PathType; }
set { Path.PathType = value; }
}
public double Distance
{
get { return Path.Distance; }
set { Path.Distance = value; }
}
public double Distance => Path.Distance;
public override Vector2 Position
{
@ -166,7 +154,7 @@ namespace osu.Game.Rulesets.Osu.Objects
private void createSliderEnds()
{
HeadCircle = new SliderCircle(this)
HeadCircle = new SliderCircle
{
StartTime = StartTime,
Position = Position,
@ -176,7 +164,7 @@ namespace osu.Game.Rulesets.Osu.Objects
ComboIndex = ComboIndex,
};
TailCircle = new SliderTailCircle(this)
TailCircle = new SliderTailCircle
{
StartTime = EndTime,
Position = EndPosition,

View File

@ -1,19 +1,9 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderCircle : HitCircle
{
private readonly Slider slider;
public SliderCircle(Slider slider)
{
this.slider = slider;
}
public override void OffsetPosition(Vector2 offset) => slider.OffsetPosition(offset);
}
}

View File

@ -8,11 +8,6 @@ namespace osu.Game.Rulesets.Osu.Objects
{
public class SliderTailCircle : SliderCircle
{
public SliderTailCircle(Slider slider)
: base(slider)
{
}
public override Judgement CreateJudgement() => new OsuSliderTailJudgement();
}
}

View File

@ -7,7 +7,6 @@ using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Judgements;
using osu.Game.Rulesets.Osu.Judgements;
using OpenTK;
namespace osu.Game.Rulesets.Osu.Objects
{
@ -32,10 +31,5 @@ namespace osu.Game.Rulesets.Osu.Objects
}
public override Judgement CreateJudgement() => new OsuJudgement();
public override void OffsetPosition(Vector2 offset)
{
// for now we don't want to allow spinners to be moved around.
}
}
}

View File

@ -84,5 +84,7 @@ namespace osu.Game.Rulesets.Osu.UI
judgementLayer.Add(explosion);
}
public override bool ReceivePositionalInputAt(Vector2 screenSpacePos) => HitObjectContainer.ReceivePositionalInputAt(screenSpacePos);
}
}

View File

@ -8,7 +8,6 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Configuration;
using osu.Game.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Judgements;
@ -39,10 +38,6 @@ namespace osu.Game.Rulesets.Taiko.UI
/// </summary>
private const float left_area_size = 240;
protected override bool UserScrollSpeedAdjustment => false;
protected override SpeedChangeVisualisationMethod VisualisationMethod => SpeedChangeVisualisationMethod.Overlapping;
private readonly Container<HitExplosion> hitExplosionContainer;
private readonly Container<KiaiHitExplosion> kiaiExplosionContainer;
private readonly JudgementContainer<DrawableTaikoJudgement> judgementContainer;
@ -60,8 +55,6 @@ namespace osu.Game.Rulesets.Taiko.UI
public TaikoPlayfield(ControlPointInfo controlPoints)
{
Direction.Value = ScrollingDirection.Left;
InternalChild = new PlayfieldAdjustmentContainer
{
Anchor = Anchor.CentreLeft,
@ -201,8 +194,6 @@ namespace osu.Game.Rulesets.Taiko.UI
}
}
};
VisibleTimeRange.Value = 7000;
}
[BackgroundDependencyLoader]

View File

@ -14,6 +14,7 @@ using osu.Game.Rulesets.UI;
using osu.Game.Rulesets.Taiko.Replays;
using System.Linq;
using osu.Framework.Input;
using osu.Game.Configuration;
using osu.Game.Input.Handlers;
using osu.Game.Rulesets.UI.Scrolling;
@ -21,9 +22,15 @@ namespace osu.Game.Rulesets.Taiko.UI
{
public class TaikoRulesetContainer : ScrollingRulesetContainer<TaikoPlayfield, TaikoHitObject>
{
protected override ScrollVisualisationMethod VisualisationMethod => ScrollVisualisationMethod.Overlapping;
protected override bool UserScrollSpeedAdjustment => false;
public TaikoRulesetContainer(Ruleset ruleset, WorkingBeatmap beatmap)
: base(ruleset, beatmap)
{
Direction.Value = ScrollingDirection.Left;
TimeRange.Value = 7000;
}
[BackgroundDependencyLoader]

View File

@ -0,0 +1,54 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
namespace osu.Game.Tests.ScrollAlgorithms
{
[TestFixture]
public class ConstantScrollTest
{
private IScrollAlgorithm algorithm;
[SetUp]
public void Setup()
{
algorithm = new ConstantScrollAlgorithm();
}
[Test]
public void TestDisplayStartTime()
{
Assert.AreEqual(-8000, algorithm.GetDisplayStartTime(2000, 10000));
Assert.AreEqual(-3000, algorithm.GetDisplayStartTime(2000, 5000));
Assert.AreEqual(2000, algorithm.GetDisplayStartTime(7000, 5000));
Assert.AreEqual(7000, algorithm.GetDisplayStartTime(17000, 10000));
}
[Test]
public void TestLength()
{
Assert.AreEqual(1f / 5, algorithm.GetLength(0, 1000, 5000, 1));
Assert.AreEqual(1f / 5, algorithm.GetLength(6000, 7000, 5000, 1));
}
[Test]
public void TestPosition()
{
Assert.AreEqual(1f / 5, algorithm.PositionAt(1000, 0, 5000, 1));
Assert.AreEqual(1f / 5, algorithm.PositionAt(6000, 5000, 5000, 1));
}
[TestCase(1000)]
[TestCase(10000)]
[TestCase(15000)]
[TestCase(20000)]
[TestCase(25000)]
public void TestTime(double time)
{
Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 0, 5000, 1), 0, 5000, 1), 0.001);
Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 5000, 5000, 1), 5000, 5000, 1), 0.001);
}
}
}

View File

@ -0,0 +1,67 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Lists;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
namespace osu.Game.Tests.ScrollAlgorithms
{
[TestFixture]
public class OverlappingScrollTest
{
private IScrollAlgorithm algorithm;
[SetUp]
public void Setup()
{
var controlPoints = new SortedList<MultiplierControlPoint>
{
new MultiplierControlPoint(0) { Velocity = 1 },
new MultiplierControlPoint(10000) { Velocity = 2f },
new MultiplierControlPoint(20000) { Velocity = 0.5f }
};
algorithm = new OverlappingScrollAlgorithm(controlPoints);
}
[Test]
public void TestDisplayStartTime()
{
Assert.AreEqual(1000, algorithm.GetDisplayStartTime(2000, 1000)); // Like constant
Assert.AreEqual(10000, algorithm.GetDisplayStartTime(10500, 1000)); // 10500 - (1000 * 0.5)
Assert.AreEqual(20000, algorithm.GetDisplayStartTime(22000, 1000)); // 23000 - (1000 / 0.5)
}
[Test]
public void TestLength()
{
Assert.AreEqual(1f / 5, algorithm.GetLength(0, 1000, 5000, 1)); // Like constant
Assert.AreEqual(1f / 5, algorithm.GetLength(10000, 10500, 5000, 1)); // (10500 - 10000) / 0.5 / 5000
Assert.AreEqual(1f / 5, algorithm.GetLength(20000, 22000, 5000, 1)); // (22000 - 20000) * 0.5 / 5000
}
[Test]
public void TestPosition()
{
// Basically same calculations as TestLength()
Assert.AreEqual(1f / 5, algorithm.PositionAt(1000, 0, 5000, 1));
Assert.AreEqual(1f / 5, algorithm.PositionAt(10500, 10000, 5000, 1));
Assert.AreEqual(1f / 5, algorithm.PositionAt(22000, 20000, 5000, 1));
}
[TestCase(1000)]
[TestCase(10000)]
[TestCase(15000)]
[TestCase(20000)]
[TestCase(25000)]
[Ignore("Disabled for now because overlapping control points have multiple time values under the same position."
+ "Ideally, scrolling should be changed to constant or sequential during editing of hitobjects.")]
public void TestTime(double time)
{
Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 0, 5000, 1), 0, 5000, 1), 0.001);
Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 5000, 5000, 1), 5000, 5000, 1), 0.001);
}
}
}

View File

@ -0,0 +1,64 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Lists;
using osu.Game.Rulesets.Timing;
using osu.Game.Rulesets.UI.Scrolling.Algorithms;
namespace osu.Game.Tests.ScrollAlgorithms
{
[TestFixture]
public class SequentialScrollTest
{
private IScrollAlgorithm algorithm;
[SetUp]
public void Setup()
{
var controlPoints = new SortedList<MultiplierControlPoint>
{
new MultiplierControlPoint(0) { Velocity = 1 },
new MultiplierControlPoint(10000) { Velocity = 2f },
new MultiplierControlPoint(20000) { Velocity = 0.5f }
};
algorithm = new SequentialScrollAlgorithm(controlPoints);
}
[Test]
public void TestDisplayStartTime()
{
// Sequential scroll algorithm approximates the start time
// This should be fixed in the future
}
[Test]
public void TestLength()
{
Assert.AreEqual(1f / 5, algorithm.GetLength(0, 1000, 5000, 1)); // Like constant
Assert.AreEqual(1f / 5, algorithm.GetLength(10000, 10500, 5000, 1)); // (10500 - 10000) / 0.5 / 5000
Assert.AreEqual(1f / 5, algorithm.GetLength(20000, 22000, 5000, 1)); // (22000 - 20000) * 0.5 / 5000
}
[Test]
public void TestPosition()
{
// Basically same calculations as TestLength()
Assert.AreEqual(1f / 5, algorithm.PositionAt(1000, 0, 5000, 1));
Assert.AreEqual(1f / 5, algorithm.PositionAt(10500, 10000, 5000, 1));
Assert.AreEqual(1f / 5, algorithm.PositionAt(22000, 20000, 5000, 1));
}
[TestCase(1000)]
[TestCase(10000)]
[TestCase(15000)]
[TestCase(20000)]
[TestCase(25000)]
public void TestTime(double time)
{
Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 0, 5000, 1), 0, 5000, 1), 0.001);
Assert.AreEqual(time, algorithm.TimeAt(algorithm.PositionAt(time, 5000, 5000, 1), 5000, 5000, 1), 0.001);
}
}
}

View File

@ -5,7 +5,8 @@ using System;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Screens.Edit.Screens.Compose;
using osu.Game.Screens.Edit;
using osu.Game.Screens.Edit.Compose.Components;
using OpenTK;
namespace osu.Game.Tests.Visual

View File

@ -0,0 +1,123 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.MathUtils;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Online.Chat;
using osu.Game.Overlays.Chat.Tabs;
using osu.Game.Users;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual
{
public class TestCaseChannelTabControl : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(ChannelTabControl),
};
private readonly ChannelTabControl channelTabControl;
public TestCaseChannelTabControl()
{
SpriteText currentText;
Add(new Container
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Children = new Drawable[]
{
channelTabControl = new ChannelTabControl
{
RelativeSizeAxes = Axes.X,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
Height = 50
},
new Box
{
Colour = Color4.Black.Opacity(0.1f),
RelativeSizeAxes = Axes.X,
Height = 50,
Depth = -1,
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
}
}
});
Add(new Container
{
Origin = Anchor.TopLeft,
Anchor = Anchor.TopLeft,
Children = new Drawable[]
{
currentText = new SpriteText
{
Text = "Currently selected channel:"
}
}
});
channelTabControl.OnRequestLeave += channel => channelTabControl.RemoveChannel(channel);
channelTabControl.Current.ValueChanged += channel => currentText.Text = "Currently selected channel: " + channel.ToString();
AddStep("Add random private channel", addRandomUser);
AddAssert("There is only one channels", () => channelTabControl.Items.Count() == 2);
AddRepeatStep("Add 3 random private channels", addRandomUser, 3);
AddAssert("There are four channels", () => channelTabControl.Items.Count() == 5);
AddStep("Add random public channel", () => addChannel(RNG.Next().ToString()));
AddRepeatStep("Select a random channel", () => channelTabControl.Current.Value = channelTabControl.Items.ElementAt(RNG.Next(channelTabControl.Items.Count())), 20);
}
private List<User> users;
private void addRandomUser()
{
channelTabControl.AddChannel(new Channel
{
Users =
{
users?.Count > 0
? users[RNG.Next(0, users.Count - 1)]
: new User
{
Id = RNG.Next(),
Username = "testuser" + RNG.Next(1000)
}
}
});
}
private void addChannel(string name)
{
channelTabControl.AddChannel(new Channel
{
Type = ChannelType.Public,
Name = name
});
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
GetUsersRequest req = new GetUsersRequest();
req.Success += list => users = list.Select(e => e.User).ToList();
api.Queue(req);
}
}
}

View File

@ -1,21 +1,45 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.ComponentModel;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Online.Chat;
using osu.Game.Overlays;
using osu.Game.Overlays.Chat;
using osu.Game.Overlays.Chat.Tabs;
namespace osu.Game.Tests.Visual
{
[Description("Testing chat api and overlay")]
public class TestCaseChatDisplay : OsuTestCase
{
public TestCaseChatDisplay()
public override IReadOnlyList<Type> RequiredTypes => new[]
{
Add(new ChatOverlay
typeof(ChatOverlay),
typeof(ChatLine),
typeof(DrawableChannel),
typeof(ChannelSelectorTabItem),
typeof(ChannelTabControl),
typeof(ChannelTabItem),
typeof(PrivateChannelTabItem),
typeof(TabCloseButton)
};
[Cached]
private readonly ChannelManager channelManager = new ChannelManager();
[BackgroundDependencyLoader]
private void load()
{
Children = new Drawable[]
{
State = Visibility.Visible
});
channelManager,
new ChatOverlay { State = Visibility.Visible }
};
}
}
}

View File

@ -50,14 +50,13 @@ namespace osu.Game.Tests.Visual
private void load(OsuColour colours)
{
linkColour = colours.Blue;
Dependencies.Cache(new ChatOverlay
{
AvailableChannels =
{
new Channel { Name = "#english" },
new Channel { Name = "#japanese" }
}
});
var chatManager = new ChannelManager();
chatManager.AvailableChannels.Add(new Channel { Name = "#english"});
chatManager.AvailableChannels.Add(new Channel { Name = "#japanese" });
Dependencies.Cache(chatManager);
Dependencies.Cache(new ChatOverlay());
testLinksGeneral();
testEcho();

View File

@ -6,7 +6,7 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Screens.Compose;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
@ -14,13 +14,13 @@ namespace osu.Game.Tests.Visual
[TestFixture]
public class TestCaseEditorCompose : EditorClockTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Compose) };
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(ComposeScreen) };
[BackgroundDependencyLoader]
private void load()
{
Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo);
Child = new Compose();
Child = new ComposeScreen();
}
}
}

View File

@ -5,7 +5,7 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Game.Screens.Edit.Screens.Compose.RadioButtons;
using osu.Game.Screens.Edit.Components.RadioButtons;
namespace osu.Game.Tests.Visual
{

View File

@ -13,7 +13,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using OpenTK.Graphics;
namespace osu.Game.Tests.Visual

View File

@ -8,7 +8,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Menus;
using osu.Game.Screens.Edit.Components.Menus;
namespace osu.Game.Tests.Visual
{

View File

@ -11,13 +11,14 @@ using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Edit;
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks;
using osu.Game.Rulesets.Osu.Edit.Masks.HitCircleMasks.Components;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles;
using osu.Game.Rulesets.Osu.Edit.Blueprints.HitCircles.Components;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Screens.Compose;
using osu.Game.Screens.Edit.Screens.Compose.Layers;
using osu.Game.Screens.Edit.Compose;
using osu.Game.Screens.Edit.Compose.Components;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
@ -28,15 +29,15 @@ namespace osu.Game.Tests.Visual
{
public override IReadOnlyList<Type> RequiredTypes => new[]
{
typeof(MaskSelection),
typeof(DragLayer),
typeof(SelectionBox),
typeof(DragBox),
typeof(HitObjectComposer),
typeof(OsuHitObjectComposer),
typeof(HitObjectMaskLayer),
typeof(BlueprintContainer),
typeof(NotNullAttribute),
typeof(HitCirclePiece),
typeof(HitCircleSelectionMask),
typeof(HitCirclePlacementMask),
typeof(HitCircleSelectionBlueprint),
typeof(HitCirclePlacementBlueprint),
};
private HitObjectComposer composer;
@ -53,12 +54,11 @@ namespace osu.Game.Tests.Visual
new Slider
{
Position = new Vector2(128, 256),
ControlPoints = new[]
Path = new SliderPath(PathType.Linear, new[]
{
Vector2.Zero,
new Vector2(216, 0),
},
Distance = 216,
}),
Scale = 0.5f,
}
},

View File

@ -13,25 +13,25 @@ using OpenTK.Input;
namespace osu.Game.Tests.Visual
{
[Description("'Hold to Quit' UI element")]
public class TestCaseQuitButton : ManualInputManagerTestCase
public class TestCaseHoldForMenuButton : ManualInputManagerTestCase
{
private bool exitAction;
[BackgroundDependencyLoader]
private void load()
{
QuitButton quitButton;
HoldForMenuButton holdForMenuButton;
Add(quitButton = new QuitButton
Add(holdForMenuButton = new HoldForMenuButton
{
Origin = Anchor.BottomRight,
Anchor = Anchor.BottomRight,
Action = () => exitAction = true
});
var text = quitButton.Children.OfType<SpriteText>().First();
var text = holdForMenuButton.Children.OfType<SpriteText>().First();
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(quitButton));
AddStep("Trigger text fade in", () => InputManager.MoveMouseTo(holdForMenuButton));
AddUntilStep(() => text.IsPresent && !exitAction, "Text visible");
AddStep("Trigger text fade out", () => InputManager.MoveMouseTo(Vector2.One));
AddUntilStep(() => !text.IsPresent && !exitAction, "Text is not visible");
@ -39,7 +39,7 @@ namespace osu.Game.Tests.Visual
AddStep("Trigger exit action", () =>
{
exitAction = false;
InputManager.MoveMouseTo(quitButton);
InputManager.MoveMouseTo(holdForMenuButton);
InputManager.PressButton(MouseButton.Left);
});
@ -47,7 +47,7 @@ namespace osu.Game.Tests.Visual
AddAssert("action not triggered", () => !exitAction);
AddStep("Trigger exit action", () => InputManager.PressButton(MouseButton.Left));
AddUntilStep(() => exitAction, $"{nameof(quitButton.Action)} was triggered");
AddUntilStep(() => exitAction, $"{nameof(holdForMenuButton.Action)} was triggered");
}
}
}

View File

@ -5,9 +5,9 @@ using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Screens.Edit.Screens.Setup.Components.LabelledComponents;
using System;
using System.Collections.Generic;
using osu.Game.Screens.Edit.Setup.Components.LabelledComponents;
namespace osu.Game.Tests.Visual
{

View File

@ -9,6 +9,7 @@ using OpenTK;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Game.Configuration;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Timing;
@ -22,6 +23,7 @@ namespace osu.Game.Tests.Visual
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Playfield) };
private readonly ScrollingTestContainer[] scrollContainers = new ScrollingTestContainer[4];
private readonly TestPlayfield[] playfields = new TestPlayfield[4];
public TestCaseScrollingHitObjects()
@ -33,18 +35,38 @@ namespace osu.Game.Tests.Visual
{
new Drawable[]
{
playfields[0] = new TestPlayfield(ScrollingDirection.Up),
playfields[1] = new TestPlayfield(ScrollingDirection.Down)
scrollContainers[0] = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[0] = new TestPlayfield()
},
scrollContainers[1] = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[1] = new TestPlayfield()
},
},
new Drawable[]
{
playfields[2] = new TestPlayfield(ScrollingDirection.Left),
playfields[3] = new TestPlayfield(ScrollingDirection.Right)
scrollContainers[2] = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[2] = new TestPlayfield()
},
scrollContainers[3] = new ScrollingTestContainer(ScrollingDirection.Up)
{
RelativeSizeAxes = Axes.Both,
Child = playfields[3] = new TestPlayfield()
}
}
}
});
AddSliderStep("Time range", 100, 10000, 5000, v => playfields.ForEach(p => p.VisibleTimeRange.Value = v));
AddStep("Constant scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Constant));
AddStep("Overlapping scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Overlapping));
AddStep("Sequential scroll", () => setScrollAlgorithm(ScrollVisualisationMethod.Sequential));
AddSliderStep("Time range", 100, 10000, 5000, v => scrollContainers.ForEach(c => c.TimeRange = v));
AddStep("Add control point", () => addControlPoint(Time.Current + 5000));
}
@ -52,7 +74,7 @@ namespace osu.Game.Tests.Visual
{
base.LoadComplete();
playfields.ForEach(p => p.HitObjects.AddControlPoint(new MultiplierControlPoint(0)));
scrollContainers.ForEach(c => c.ControlPoints.Add(new MultiplierControlPoint(0)));
for (int i = 0; i <= 5000; i += 1000)
addHitObject(Time.Current + i);
@ -73,12 +95,15 @@ namespace osu.Game.Tests.Visual
private void addControlPoint(double time)
{
scrollContainers.ForEach(c =>
{
c.ControlPoints.Add(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } });
c.ControlPoints.Add(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } });
c.ControlPoints.Add(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } });
});
playfields.ForEach(p =>
{
p.HitObjects.AddControlPoint(new MultiplierControlPoint(time) { DifficultyPoint = { SpeedMultiplier = 3 } });
p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 2000) { DifficultyPoint = { SpeedMultiplier = 2 } });
p.HitObjects.AddControlPoint(new MultiplierControlPoint(time + 3000) { DifficultyPoint = { SpeedMultiplier = 1 } });
TestDrawableControlPoint createDrawablePoint(double t)
{
var obj = new TestDrawableControlPoint(p.Direction, t);
@ -111,15 +136,14 @@ namespace osu.Game.Tests.Visual
}
}
private void setScrollAlgorithm(ScrollVisualisationMethod algorithm) => scrollContainers.ForEach(c => c.ScrollAlgorithm = algorithm);
private class TestPlayfield : ScrollingPlayfield
{
public new readonly ScrollingDirection Direction;
public new ScrollingDirection Direction => base.Direction.Value;
public TestPlayfield(ScrollingDirection direction)
public TestPlayfield()
{
Direction = direction;
Padding = new MarginPadding(2);
InternalChildren = new Drawable[]

View File

@ -10,7 +10,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Graphics.Cursor;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
using osu.Game.Screens.Edit.Compose.Components.Timeline;
using OpenTK;
using OpenTK.Graphics;

View File

@ -5,7 +5,7 @@ using System.ComponentModel;
namespace osu.Game.Configuration
{
public enum SpeedChangeVisualisationMethod
public enum ScrollVisualisationMethod
{
[Description("Sequential")]
Sequential,

View File

@ -7,6 +7,7 @@ using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Graphics.Sprites;
using System.Collections.Generic;
using osu.Framework.Logging;
using osu.Framework.Platform;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
@ -21,16 +22,17 @@ namespace osu.Game.Graphics.Containers
}
private OsuGame game;
private ChannelManager channelManager;
private Action showNotImplementedError;
private GameHost host;
[BackgroundDependencyLoader(true)]
private void load(OsuGame game, NotificationOverlay notifications, GameHost host)
private void load(OsuGame game, NotificationOverlay notifications, GameHost host, ChannelManager channelManager)
{
// will be null in tests
this.game = game;
this.host = host;
this.channelManager = channelManager;
showNotImplementedError = () => notifications?.Post(new SimpleNotification
{
@ -80,7 +82,15 @@ namespace osu.Game.Graphics.Containers
game?.ShowBeatmapSet(setId);
break;
case LinkAction.OpenChannel:
game?.OpenChannel(linkArgument);
try
{
channelManager?.OpenChannel(linkArgument);
}
catch (ChannelNotFoundException e)
{
Logger.Log($"The requested channel \"{linkArgument}\" does not exist");
}
break;
case LinkAction.OpenEditorTimestamp:
case LinkAction.JoinMultiplayerMatch:

View File

@ -2,9 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.ComponentModel;
using System.Reflection;
using System.Collections.Generic;
namespace osu.Game.Graphics.UserInterface
{
@ -15,18 +12,7 @@ namespace osu.Game.Graphics.UserInterface
if (!typeof(T).IsEnum)
throw new InvalidOperationException("OsuEnumDropdown only supports enums as the generic type argument");
List<KeyValuePair<string, T>> items = new List<KeyValuePair<string, T>>();
foreach (var val in (T[])Enum.GetValues(typeof(T)))
{
var field = typeof(T).GetField(Enum.GetName(typeof(T), val));
items.Add(
new KeyValuePair<string, T>(
field.GetCustomAttribute<DescriptionAttribute>()?.Description ?? Enum.GetName(typeof(T), val),
val
)
);
}
Items = items;
Items = (T[])Enum.GetValues(typeof(T));
}
}
}

View File

@ -0,0 +1,28 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Collections.Generic;
using osu.Framework.IO.Network;
using osu.Game.Online.Chat;
namespace osu.Game.Online.API
{
public abstract class APIMessagesRequest : APIRequest<List<Message>>
{
private readonly long? sinceId;
protected APIMessagesRequest(long? sinceId)
{
this.sinceId = sinceId;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
if (sinceId.HasValue) req.AddParameter(@"since", sinceId.Value.ToString());
return req;
}
}
}

View File

@ -0,0 +1,34 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System.Net.Http;
using osu.Framework.IO.Network;
using osu.Game.Online.Chat;
using osu.Game.Users;
namespace osu.Game.Online.API.Requests
{
public class CreateNewPrivateMessageRequest : APIRequest<CreateNewPrivateMessageResponse>
{
private readonly User user;
private readonly Message message;
public CreateNewPrivateMessageRequest(User user, Message message)
{
this.user = user;
this.message = message;
}
protected override WebRequest CreateWebRequest()
{
var req = base.CreateWebRequest();
req.Method = HttpMethod.Post;
req.AddParameter(@"target_id", user.Id.ToString());
req.AddParameter(@"message", message.Content);
req.AddParameter(@"is_action", message.IsAction.ToString().ToLowerInvariant());
return req;
}
protected override string Target => @"chat/new";
}
}

View File

@ -0,0 +1,16 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using Newtonsoft.Json;
using osu.Game.Online.Chat;
namespace osu.Game.Online.API.Requests
{
public class CreateNewPrivateMessageResponse
{
[JsonProperty("new_channel_id")]
public int ChannelID;
public Message Message;
}
}

View File

@ -3,15 +3,64 @@
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using Newtonsoft.Json;
using osu.Framework.Configuration;
using osu.Framework.Lists;
using osu.Game.Users;
namespace osu.Game.Online.Chat
{
public class Channel
{
public readonly int MaxHistory = 300;
/// <summary>
/// Contains every joined user except the current logged in user. Currently only returned for PM channels.
/// </summary>
public readonly ObservableCollection<User> Users = new ObservableCollection<User>();
[JsonProperty(@"users")]
private long[] userIds
{
set
{
foreach (var id in value)
Users.Add(new User { Id = id });
}
}
/// <summary>
/// Contains all the messages send in the channel.
/// </summary>
public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
/// <summary>
/// Contains all the messages that are still pending for submission to the server.
/// </summary>
private readonly List<LocalEchoMessage> pendingMessages = new List<LocalEchoMessage>();
/// <summary>
/// An event that fires when new messages arrived.
/// </summary>
public event Action<IEnumerable<Message>> NewMessagesArrived;
/// <summary>
/// An event that fires when a pending message gets resolved.
/// </summary>
public event Action<LocalEchoMessage, Message> PendingMessageResolved;
/// <summary>
/// An event that fires when a pending message gets removed.
/// </summary>
public event Action<Message> MessageRemoved;
public bool ReadOnly => false; //todo not yet used.
public override string ToString() => Name;
[JsonProperty(@"name")]
public string Name;
@ -22,19 +71,16 @@ namespace osu.Game.Online.Chat
public ChannelType Type;
[JsonProperty(@"channel_id")]
public int Id;
public long Id;
[JsonProperty(@"last_message_id")]
public long? LastMessageId;
public readonly SortedList<Message> Messages = new SortedList<Message>(Comparer<Message>.Default);
private readonly List<LocalEchoMessage> pendingMessages = new List<LocalEchoMessage>();
/// <summary>
/// Signalles if the current user joined this channel or not. Defaults to false.
/// </summary>
public Bindable<bool> Joined = new Bindable<bool>();
public bool ReadOnly => false;
public const int MAX_HISTORY = 300;
[JsonConstructor]
@ -42,10 +88,10 @@ namespace osu.Game.Online.Chat
{
}
public event Action<IEnumerable<Message>> NewMessagesArrived;
public event Action<LocalEchoMessage, Message> PendingMessageResolved;
public event Action<Message> MessageRemoved;
/// <summary>
/// Adds the argument message as a local echo. When this local echo is resolved <see cref="PendingMessageResolved"/> will get called.
/// </summary>
/// <param name="message"></param>
public void AddLocalEcho(LocalEchoMessage message)
{
pendingMessages.Add(message);
@ -54,8 +100,12 @@ namespace osu.Game.Online.Chat
NewMessagesArrived?.Invoke(new[] { message });
}
public bool MessagesLoaded { get; private set; }
public bool MessagesLoaded;
/// <summary>
/// Adds new messages to the channel and purges old messages. Triggers the <see cref="NewMessagesArrived"/> event.
/// </summary>
/// <param name="messages"></param>
public void AddNewMessages(params Message[] messages)
{
messages = messages.Except(Messages).ToArray();
@ -63,7 +113,6 @@ namespace osu.Game.Online.Chat
if (messages.Length == 0) return;
Messages.AddRange(messages);
MessagesLoaded = true;
var maxMessageId = messages.Max(m => m.Id);
if (maxMessageId > LastMessageId)
@ -74,14 +123,6 @@ namespace osu.Game.Online.Chat
NewMessagesArrived?.Invoke(messages);
}
private void purgeOldMessages()
{
// 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>
@ -101,17 +142,18 @@ namespace osu.Game.Online.Chat
}
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;
}
throw new InvalidOperationException("Attempted to add the same message again");
Messages.Add(final);
PendingMessageResolved?.Invoke(echo, final);
}
public override string ToString() => Name;
private void purgeOldMessages()
{
// never purge local echos
int messageCount = Messages.Count - pendingMessages.Count;
if (messageCount > MaxHistory)
Messages.RemoveRange(0, messageCount - MaxHistory);
}
}
}

View File

@ -0,0 +1,409 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using System.Collections.ObjectModel;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Logging;
using osu.Framework.Threading;
using osu.Game.Online.API;
using osu.Game.Online.API.Requests;
using osu.Game.Users;
namespace osu.Game.Online.Chat
{
/// <summary>
/// Manages everything channel related
/// </summary>
public class ChannelManager : Component, IOnlineComponent
{
/// <summary>
/// The channels the player joins on startup
/// </summary>
private readonly string[] defaultChannels =
{
@"#lazer",
@"#osu",
@"#lobby"
};
/// <summary>
/// The currently opened channel
/// </summary>
public Bindable<Channel> CurrentChannel { get; } = new Bindable<Channel>();
/// <summary>
/// The Channels the player has joined
/// </summary>
public ObservableCollection<Channel> JoinedChannels { get; } = new ObservableCollection<Channel>(); //todo: should be publicly readonly
/// <summary>
/// The channels available for the player to join
/// </summary>
public ObservableCollection<Channel> AvailableChannels { get; } = new ObservableCollection<Channel>(); //todo: should be publicly readonly
private IAPIProvider api;
private ScheduledDelegate fetchMessagesScheduleder;
public ChannelManager()
{
CurrentChannel.ValueChanged += currentChannelChanged;
}
/// <summary>
/// Opens a channel or switches to the channel if already opened.
/// </summary>
/// <exception cref="ChannelNotFoundException">If the name of the specifed channel was not found this exception will be thrown.</exception>
/// <param name="name"></param>
public void OpenChannel(string name)
{
if (name == null)
throw new ArgumentNullException(nameof(name));
CurrentChannel.Value = AvailableChannels.FirstOrDefault(c => c.Name == name) ?? throw new ChannelNotFoundException(name);
}
/// <summary>
/// Opens a new private channel.
/// </summary>
/// <param name="user">The user the private channel is opened with.</param>
public void OpenPrivateChannel(User user)
{
if (user == null)
throw new ArgumentNullException(nameof(user));
CurrentChannel.Value = JoinedChannels.FirstOrDefault(c => c.Type == ChannelType.PM && c.Users.Count == 1 && c.Users.Any(u => u.Id == user.Id))
?? new Channel { Name = user.Username, Users = { user } };
}
private void currentChannelChanged(Channel channel) => JoinChannel(channel);
/// <summary>
/// Ensure we run post actions in sequence, once at a time.
/// </summary>
private readonly Queue<Action> postQueue = new Queue<Action>();
/// <summary>
/// Posts a message to the currently opened channel.
/// </summary>
/// <param name="text">The message text that is going to be posted</param>
/// <param name="isAction">Is true if the message is an action, e.g.: user is currently eating </param>
public void PostMessage(string text, bool isAction = false)
{
if (CurrentChannel.Value == null)
return;
var currentChannel = CurrentChannel.Value;
void dequeueAndRun()
{
if (postQueue.Count > 0)
postQueue.Dequeue().Invoke();
}
postQueue.Enqueue(() =>
{
if (!api.IsLoggedIn)
{
currentChannel.AddNewMessages(new ErrorMessage("Please sign in to participate in chat!"));
return;
}
var message = new LocalEchoMessage
{
Sender = api.LocalUser.Value,
Timestamp = DateTimeOffset.Now,
ChannelId = CurrentChannel.Value.Id,
IsAction = isAction,
Content = text
};
currentChannel.AddLocalEcho(message);
// if this is a PM and the first message, we need to do a special request to create the PM channel
if (currentChannel.Type == ChannelType.PM && !currentChannel.Joined)
{
var createNewPrivateMessageRequest = new CreateNewPrivateMessageRequest(currentChannel.Users.First(), message);
createNewPrivateMessageRequest.Success += createRes =>
{
currentChannel.Id = createRes.ChannelID;
currentChannel.ReplaceMessage(message, createRes.Message);
dequeueAndRun();
};
createNewPrivateMessageRequest.Failure += exception =>
{
Logger.Error(exception, "Posting message failed.");
currentChannel.ReplaceMessage(message, null);
dequeueAndRun();
};
api.Queue(createNewPrivateMessageRequest);
return;
}
var req = new PostMessageRequest(message);
req.Success += m =>
{
currentChannel.ReplaceMessage(message, m);
dequeueAndRun();
};
req.Failure += exception =>
{
Logger.Error(exception, "Posting message failed.");
currentChannel.ReplaceMessage(message, null);
dequeueAndRun();
};
api.Queue(req);
});
// always run if the queue is empty
if (postQueue.Count == 1)
dequeueAndRun();
}
/// <summary>
/// Posts a command locally. Commands like /help will result in a help message written in the current channel.
/// </summary>
/// <param name="text">the text containing the command identifier and command parameters.</param>
public void PostCommand(string text)
{
if (CurrentChannel.Value == null)
return;
var parameters = text.Split(new[] { ' ' }, 2);
string command = parameters[0];
string content = parameters.Length == 2 ? parameters[1] : string.Empty;
switch (command)
{
case "me":
if (string.IsNullOrWhiteSpace(content))
{
CurrentChannel.Value.AddNewMessages(new ErrorMessage("Usage: /me [action]"));
break;
}
PostMessage(content, true);
break;
case "help":
CurrentChannel.Value.AddNewMessages(new InfoMessage("Supported commands: /help, /me [action]"));
break;
default:
CurrentChannel.Value.AddNewMessages(new ErrorMessage($@"""/{command}"" is not supported! For a list of supported commands see /help"));
break;
}
}
private void handleChannelMessages(IEnumerable<Message> messages)
{
var channels = JoinedChannels.ToList();
foreach (var group in messages.GroupBy(m => m.ChannelId))
channels.Find(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
}
private void initializeChannels()
{
var req = new ListChannelsRequest();
var joinDefaults = JoinedChannels.Count == 0;
req.Success += channels =>
{
foreach (var channel in channels)
{
// add as available if not already
if (AvailableChannels.All(c => c.Id != channel.Id))
AvailableChannels.Add(channel);
// join any channels classified as "defaults"
if (joinDefaults && defaultChannels.Any(c => c.Equals(channel.Name, StringComparison.OrdinalIgnoreCase)))
JoinChannel(channel);
}
};
req.Failure += error =>
{
Logger.Error(error, "Fetching channel list failed");
initializeChannels();
};
api.Queue(req);
}
/// <summary>
/// Fetches inital messages of a channel
///
/// TODO: remove this when the API supports returning initial fetch messages for more than one channel by specifying the last message id per channel instead of one last message id globally.
/// right now it caps out at 50 messages and therefore only returns one channel's worth of content.
/// </summary>
/// <param name="channel">The channel </param>
private void fetchInitalMessages(Channel channel)
{
if (channel.Id <= 0) return;
var fetchInitialMsgReq = new GetMessagesRequest(channel);
fetchInitialMsgReq.Success += messages =>
{
handleChannelMessages(messages);
channel.MessagesLoaded = true; // this will mark the channel as having received messages even if there were none.
};
api.Queue(fetchInitialMsgReq);
}
public void JoinChannel(Channel channel)
{
if (channel == null) return;
// ReSharper disable once AccessToModifiedClosure
var existing = JoinedChannels.FirstOrDefault(c => c.Id == channel.Id);
if (existing != null)
{
// if we already have this channel loaded, we don't want to make a second one.
channel = existing;
}
else
{
var foundSelf = channel.Users.FirstOrDefault(u => u.Id == api.LocalUser.Value.Id);
if (foundSelf != null)
channel.Users.Remove(foundSelf);
JoinedChannels.Add(channel);
if (channel.Type == ChannelType.Public && !channel.Joined)
{
var req = new JoinChannelRequest(channel, api.LocalUser);
req.Success += () =>
{
channel.Joined.Value = true;
JoinChannel(channel);
};
req.Failure += ex => LeaveChannel(channel);
api.Queue(req);
return;
}
}
if (CurrentChannel.Value == null)
CurrentChannel.Value = channel;
if (!channel.MessagesLoaded)
{
// let's fetch a small number of messages to bring us up-to-date with the backlog.
fetchInitalMessages(channel);
}
}
public void LeaveChannel(Channel channel)
{
if (channel == null) return;
if (channel == CurrentChannel.Value) CurrentChannel.Value = null;
JoinedChannels.Remove(channel);
if (channel.Joined.Value)
{
api.Queue(new LeaveChannelRequest(channel, api.LocalUser));
channel.Joined.Value = false;
}
}
public void APIStateChanged(APIAccess api, APIState state)
{
switch (state)
{
case APIState.Online:
fetchUpdates();
break;
default:
fetchMessagesScheduleder?.Cancel();
fetchMessagesScheduleder = null;
break;
}
}
private long lastMessageId;
private const int update_poll_interval = 1000;
private bool channelsInitialised;
private void fetchUpdates()
{
fetchMessagesScheduleder?.Cancel();
fetchMessagesScheduleder = Scheduler.AddDelayed(() =>
{
var fetchReq = new GetUpdatesRequest(lastMessageId);
fetchReq.Success += updates =>
{
if (updates?.Presence != null)
{
foreach (var channel in updates.Presence)
{
if (!channel.Joined.Value)
{
// we received this from the server so should mark the channel already joined.
channel.Joined.Value = true;
JoinChannel(channel);
}
}
if (!channelsInitialised)
{
channelsInitialised = true;
// we want this to run after the first presence so we can see if the user is in any channels already.
initializeChannels();
}
//todo: handle left channels
handleChannelMessages(updates.Messages);
foreach (var group in updates.Messages.GroupBy(m => m.ChannelId))
JoinedChannels.FirstOrDefault(c => c.Id == group.Key)?.AddNewMessages(group.ToArray());
lastMessageId = updates.Messages.LastOrDefault()?.Id ?? lastMessageId;
}
fetchUpdates();
};
fetchReq.Failure += delegate { fetchUpdates(); };
api.Queue(fetchReq);
}, update_poll_interval);
}
[BackgroundDependencyLoader]
private void load(IAPIProvider api)
{
this.api = api;
api.Register(this);
}
}
/// <summary>
/// An exception thrown when a channel could not been found.
/// </summary>
public class ChannelNotFoundException : Exception
{
public ChannelNotFoundException(string channelName)
: base($"A channel with the name {channelName} could not be found.")
{
}
}
}

View File

@ -3,7 +3,6 @@
using System;
using System.Collections.Generic;
using System.ComponentModel;
using Newtonsoft.Json;
using osu.Game.Users;
@ -16,10 +15,10 @@ namespace osu.Game.Online.Chat
//todo: this should be inside sender.
[JsonProperty(@"sender_id")]
public int UserId;
public long UserId;
[JsonProperty(@"channel_id")]
public int ChannelId;
public long ChannelId;
[JsonProperty(@"is_action")]
public bool IsAction;
@ -69,12 +68,4 @@ namespace osu.Game.Online.Chat
// ReSharper disable once ImpureMethodCallOnReadonlyValueField
public override int GetHashCode() => Id.GetHashCode();
}
public enum TargetType
{
[Description(@"channel")]
Channel,
[Description(@"user")]
User
}
}

View File

@ -31,6 +31,7 @@ using osu.Game.Overlays.Notifications;
using osu.Game.Rulesets;
using osu.Game.Screens.Play;
using osu.Game.Input.Bindings;
using osu.Game.Online.Chat;
using osu.Game.Rulesets.Mods;
using osu.Game.Skinning;
using OpenTK.Graphics;
@ -180,12 +181,6 @@ namespace osu.Game
private ScheduledDelegate scoreLoad;
/// <summary>
/// Open chat to a channel matching the provided name, if present.
/// </summary>
/// <param name="channelName">The name of the channel.</param>
public void OpenChannel(string channelName) => chat.OpenChannel(chat.AvailableChannels.Find(c => c.Name == channelName));
/// <summary>
/// Show a beatmap set as an overlay.
/// </summary>
@ -343,6 +338,11 @@ namespace osu.Game
//overlay elements
loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
loadComponentSingleFile(social = new SocialOverlay { Depth = -1 }, mainContent.Add);
loadComponentSingleFile(new ChannelManager(), channelManager =>
{
dependencies.Cache(channelManager);
AddInternal(channelManager);
});
loadComponentSingleFile(chat = new ChatOverlay { Depth = -1 }, mainContent.Add);
loadComponentSingleFile(settings = new MainSettings
{

View File

@ -1,6 +1,7 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Linq;
using OpenTK;
using OpenTK.Graphics;
@ -81,6 +82,8 @@ namespace osu.Game.Overlays.Chat
Padding = new MarginPadding { Left = padding, Right = padding };
}
private ChannelManager chatManager;
private Message message;
private OsuSpriteText username;
private LinkFlowContainer contentFlow;
@ -104,9 +107,9 @@ namespace osu.Game.Overlays.Chat
}
[BackgroundDependencyLoader(true)]
private void load(OsuColour colours, ChatOverlay chat)
private void load(OsuColour colours, ChannelManager chatManager)
{
this.chat = chat;
this.chatManager = chatManager;
customUsernameColour = colours.ChatBlue;
}
@ -215,8 +218,6 @@ namespace osu.Game.Overlays.Chat
FinishTransforms(true);
}
private ChatOverlay chat;
private void updateMessageContent()
{
this.FadeTo(message is LocalEchoMessage ? 0.4f : 1.0f, 500, Easing.OutQuint);
@ -226,7 +227,7 @@ namespace osu.Game.Overlays.Chat
username.Text = $@"{message.Sender.Username}" + (senderHasBackground || message.IsAction ? "" : ":");
// remove non-existent channels from the link list
message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chat?.AvailableChannels.Any(c => c.Name == link.Argument) != true);
message.Links.RemoveAll(link => link.Action == LinkAction.OpenChannel && chatManager?.AvailableChannels.Any(c => c.Name == link.Argument) != true);
contentFlow.Clear();
contentFlow.AddLinks(message.DisplayContent, message.Links);
@ -236,20 +237,24 @@ namespace osu.Game.Overlays.Chat
{
private readonly User sender;
private Action startChatAction;
public MessageSender(User sender)
{
this.sender = sender;
}
[BackgroundDependencyLoader(true)]
private void load(UserProfileOverlay profile)
private void load(UserProfileOverlay profile, ChannelManager chatManager)
{
Action = () => profile?.ShowUser(sender);
startChatAction = () => chatManager?.OpenPrivateChannel(sender);
}
public MenuItem[] ContextMenuItems => new MenuItem[]
{
new OsuMenuItem("View Profile", MenuItemType.Highlighted, Action),
new OsuMenuItem("Start Chat", MenuItemType.Standard, startChatAction),
};
}
}

View File

@ -6,7 +6,6 @@ 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;
using osu.Game.Graphics.Containers;
@ -55,15 +54,11 @@ namespace osu.Game.Overlays.Chat
Channel.PendingMessageResolved += pendingMessageResolved;
}
[BackgroundDependencyLoader]
private void load()
{
newMessagesArrived(Channel.Messages);
}
protected override void LoadComplete()
{
base.LoadComplete();
newMessagesArrived(Channel.Messages);
scrollToEnd();
}
@ -79,7 +74,7 @@ namespace osu.Game.Overlays.Chat
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));
var displayMessages = newMessages.Skip(Math.Max(0, newMessages.Count() - Channel.MaxHistory));
flow.AddRange(displayMessages.Select(m => new ChatLine(m)));
@ -89,7 +84,7 @@ namespace osu.Game.Overlays.Chat
scrollToEnd();
var staleMessages = flow.Children.Where(c => c.LifetimeEnd == double.MaxValue).ToArray();
int count = staleMessages.Length - Channel.MAX_HISTORY;
int count = staleMessages.Length - Channel.MaxHistory;
for (int i = 0; i < count; i++)
{

View File

@ -15,7 +15,7 @@ using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat
namespace osu.Game.Overlays.Chat.Selection
{
public class ChannelListItem : OsuClickableContainer, IFilterable
{

View File

@ -9,7 +9,7 @@ using osu.Framework.Graphics.Containers;
using osu.Game.Graphics.Sprites;
using osu.Game.Online.Chat;
namespace osu.Game.Overlays.Chat
namespace osu.Game.Overlays.Chat.Selection
{
public class ChannelSection : Container, IHasFilterableChildren
{

View File

@ -18,7 +18,7 @@ using osu.Game.Graphics.UserInterface;
using osu.Game.Online.Chat;
using osu.Game.Graphics.Containers;
namespace osu.Game.Overlays.Chat
namespace osu.Game.Overlays.Chat.Selection
{
public class ChannelSelectionOverlay : OsuFocusedOverlayContainer
{
@ -35,23 +35,6 @@ namespace osu.Game.Overlays.Chat
public Action<Channel> OnRequestJoin;
public Action<Channel> OnRequestLeave;
public IEnumerable<ChannelSection> Sections
{
set
{
sectionsFlow.ChildrenEnumerable = value;
foreach (ChannelSection s in sectionsFlow.Children)
{
foreach (ChannelListItem c in s.ChannelFlow.Children)
{
c.OnRequestJoin = channel => { OnRequestJoin?.Invoke(channel); };
c.OnRequestLeave = channel => { OnRequestLeave?.Invoke(channel); };
}
}
}
}
public ChannelSelectionOverlay()
{
RelativeSizeAxes = Axes.X;
@ -140,6 +123,30 @@ namespace osu.Game.Overlays.Chat
search.Current.ValueChanged += newValue => sectionsFlow.SearchTerm = newValue;
}
public void UpdateAvailableChannels(IEnumerable<Channel> channels)
{
Scheduler.Add(() =>
{
sectionsFlow.ChildrenEnumerable = new[]
{
new ChannelSection
{
Header = "All Channels",
Channels = channels,
},
};
foreach (ChannelSection s in sectionsFlow.Children)
{
foreach (ChannelListItem c in s.ChannelFlow.Children)
{
c.OnRequestJoin = channel => { OnRequestJoin?.Invoke(channel); };
c.OnRequestLeave = channel => { OnRequestLeave?.Invoke(channel); };
}
}
});
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{

View File

@ -0,0 +1,32 @@
// Copyright (c) 2007-2018 ppy Pty Ltd <contact@ppy.sh>.
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Allocation;
using osu.Game.Graphics;
using osu.Game.Online.Chat;
namespace osu.Game.Overlays.Chat.Tabs
{
public class ChannelSelectorTabItem : ChannelTabItem
{
public override bool IsRemovable => false;
public ChannelSelectorTabItem(Channel value) : base(value)
{
Depth = float.MaxValue;
Width = 45;
Icon.Alpha = 0;
Text.TextSize = 45;
TextBold.TextSize = 45;
}
[BackgroundDependencyLoader]
private new void load(OsuColour colour)
{
BackgroundInactive = colour.Gray2;
BackgroundActive = colour.Gray3;
}
}
}

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