1
0
mirror of https://github.com/ppy/osu.git synced 2026-05-15 06:22:34 +08:00

Compare commits

...

186 Commits

103 changed files with 1727 additions and 654 deletions
+3 -5
View File
@@ -16,11 +16,9 @@
<language>en-AU</language>
</metadata>
<files>
<file src="*.exe" target="lib\net45\" exclude="**vshost**"/>
<file src="*.dll" target="lib\net45\"/>
<file src="*.config" target="lib\net45\"/>
<file src="x86\*.dll" target="lib\net45\x86\"/>
<file src="x64\*.dll" target="lib\net45\x64\"/>
<file src="**.exe" target="lib\net45\" exclude="**vshost**"/>
<file src="**.dll" target="lib\net45\"/>
<file src="**.config" target="lib\net45\"/>
</files>
</package>
@@ -16,29 +16,13 @@ namespace osu.Game.Rulesets.Catch.Beatmaps
{
public override void PostProcess(Beatmap<CatchHitObject> beatmap)
{
if (beatmap.ComboColours.Count == 0)
return;
int index = 0;
int colourIndex = 0;
CatchHitObject lastObj = null;
initialiseHyperDash(beatmap.HitObjects);
base.PostProcess(beatmap);
int index = 0;
foreach (var obj in beatmap.HitObjects)
{
if (obj.NewCombo)
{
if (lastObj != null) lastObj.LastInCombo = true;
colourIndex = (colourIndex + 1) % beatmap.ComboColours.Count;
}
obj.IndexInBeatmap = index++;
obj.ComboColour = beatmap.ComboColours[colourIndex];
lastObj = obj;
}
}
private void initialiseHyperDash(List<CatchHitObject> objects)
@@ -3,7 +3,6 @@
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
@@ -32,25 +31,11 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Banana
{
Samples = Samples,
ComboColour = getNextComboColour(),
StartTime = i,
X = RNG.NextSingle()
});
}
private Color4 getNextComboColour()
{
switch (RNG.Next(0, 3))
{
default:
return new Color4(255, 240, 0, 255);
case 1:
return new Color4(255, 192, 0, 255);
case 2:
return new Color4(214, 221, 28, 255);
}
}
public double EndTime => StartTime + Duration;
public double Duration { get; set; }
@@ -5,24 +5,25 @@ using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects
{
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasCombo
public abstract class CatchHitObject : HitObject, IHasXPosition, IHasComboInformation
{
public const double OBJECT_RADIUS = 44;
public float X { get; set; }
public Color4 ComboColour { get; set; }
public int IndexInBeatmap { get; set; }
public virtual FruitVisualRepresentation VisualRepresentation => (FruitVisualRepresentation)(IndexInBeatmap % 4);
public virtual bool NewCombo { get; set; }
public int IndexInCurrentCombo { get; set; }
public int ComboIndex { get; set; }
/// <summary>
/// The next fruit starts a new combo. Used for explodey.
/// </summary>
@@ -8,6 +8,8 @@ using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.Objects.Types;
using OpenTK;
using osu.Game.Rulesets.Scoring;
using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
@@ -57,6 +59,14 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
AddJudgement(new Judgement { Result = CheckPosition.Invoke(HitObject) ? HitResult.Perfect : HitResult.Miss });
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
private const float preempt = 1000;
protected override void UpdateState(ArmedState state)
@@ -5,28 +5,39 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Objects.Drawable
{
public class DrawableDroplet : PalpableCatchHitObject<Droplet>
{
private Pulp pulp;
public DrawableDroplet(Droplet h)
: base(h)
{
Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS) / 4;
AccentColour = h.ComboColour;
Masking = false;
}
[BackgroundDependencyLoader]
private void load()
{
InternalChild = new Pulp
InternalChild = pulp = new Pulp
{
AccentColour = AccentColour,
Size = Size
};
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
base.AccentColour = value;
pulp.AccentColour = AccentColour;
}
}
}
}
@@ -24,7 +24,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
Origin = Anchor.Centre;
Size = new Vector2((float)CatchHitObject.OBJECT_RADIUS);
AccentColour = HitObject.ComboColour;
Masking = false;
Rotation = (float)(RNG.NextDouble() - 0.5f) * 40;
@@ -33,6 +32,9 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
[BackgroundDependencyLoader]
private void load()
{
// todo: this should come from the skin.
AccentColour = colourForRrepesentation(HitObject.VisualRepresentation);
InternalChildren = new[]
{
createPulp(HitObject.VisualRepresentation),
@@ -273,5 +275,31 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
border.Alpha = (float)MathHelper.Clamp((HitObject.StartTime - Time.Current) / 500, 0, 1);
}
private Color4 colourForRrepesentation(FruitVisualRepresentation representation)
{
switch (representation)
{
default:
case FruitVisualRepresentation.Pear:
return new Color4(17, 136, 170, 255);
case FruitVisualRepresentation.Grape:
return new Color4(204, 102, 0, 255);
case FruitVisualRepresentation.Raspberry:
return new Color4(121, 9, 13, 255);
case FruitVisualRepresentation.Pineapple:
return new Color4(102, 136, 0, 255);
case FruitVisualRepresentation.Banana:
switch (RNG.Next(0, 3))
{
default:
return new Color4(255, 240, 0, 255);
case 1:
return new Color4(255, 192, 0, 255);
case 2:
return new Color4(214, 221, 28, 255);
}
}
}
}
}
@@ -33,7 +33,6 @@ namespace osu.Game.Rulesets.Catch.Objects.Drawable
var catchObject = (DrawableCatchHitObject)h;
catchObject.CheckPosition = o => CheckPosition?.Invoke(o) ?? false;
catchObject.AccentColour = HitObject.ComboColour;
dropletContainer.Add(h);
base.AddNested(h);
+38 -37
View File
@@ -60,65 +60,66 @@ namespace osu.Game.Rulesets.Catch.Objects
AddNested(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = StartTime,
X = X
});
for (var span = 0; span < this.SpanCount(); span++)
double lastDropletTime = StartTime;
for (int span = 0; span < this.SpanCount(); span++)
{
var spanStartTime = StartTime + span * spanDuration;
var reversed = span % 2 == 1;
for (var d = tickDistance; d <= length; d += tickDistance)
for (double d = 0; d <= length; d += tickDistance)
{
if (d > length - minDistanceFromEnd)
break;
var timeProgress = d / length;
var distanceProgress = reversed ? 1 - timeProgress : timeProgress;
var lastTickTime = spanStartTime + timeProgress * spanDuration;
AddNested(new Droplet
double time = spanStartTime + timeProgress * spanDuration;
double tinyTickInterval = time - lastDropletTime;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
for (double t = lastDropletTime + tinyTickInterval; t < time; t += tinyTickInterval)
{
StartTime = lastTickTime,
ComboColour = ComboColour,
X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
double progress = reversed ? 1 - (t - spanStartTime) / spanDuration : (t - spanStartTime) / spanDuration;
AddNested(new TinyDroplet
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
StartTime = t,
X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
double tinyTickInterval = tickDistance / length * spanDuration;
while (tinyTickInterval > 100)
tinyTickInterval /= 2;
for (double t = 0; t < spanDuration; t += tinyTickInterval)
{
double progress = reversed ? 1 - t / spanDuration : t / spanDuration;
AddNested(new TinyDroplet
if (d > minDistanceFromEnd && Math.Abs(d - length) > minDistanceFromEnd)
{
StartTime = spanStartTime + t,
ComboColour = ComboColour,
X = X + Curve.PositionAt(progress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
AddNested(new Droplet
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
StartTime = time,
X = X + Curve.PositionAt(distanceProgress).X / CatchPlayfield.BASE_WIDTH,
Samples = new List<SampleInfo>(Samples.Select(s => new SampleInfo
{
Bank = s.Bank,
Name = @"slidertick",
Volume = s.Volume
}))
});
}
lastDropletTime = time;
}
AddNested(new Fruit
{
Samples = Samples,
ComboColour = ComboColour,
StartTime = spanStartTime + spanDuration,
X = X + Curve.PositionAt(reversed ? 0 : 1).X / CatchPlayfield.BASE_WIDTH
});
@@ -18,7 +18,7 @@ namespace osu.Game.Rulesets.Catch.Tests
{
protected override string ResourceAssembly => "osu.Game.Rulesets.Catch";
[TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2149")]
[TestCase("basic"), Ignore("See: https://github.com/ppy/osu/issues/2232")]
public new void Test(string name)
{
base.Test(name);
@@ -6,13 +6,11 @@ using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.MathUtils;
using osu.Game.Rulesets.Catch.Objects;
using osu.Game.Rulesets.Catch.Objects.Drawable;
using osu.Game.Rulesets.Catch.Objects.Drawable.Pieces;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Catch.Tests
{
@@ -62,8 +60,6 @@ namespace osu.Game.Rulesets.Catch.Tests
Scale = 1.5f,
};
fruit.ComboColour = colourForRrepesentation(fruit.VisualRepresentation);
return new DrawableFruit(fruit)
{
Anchor = Anchor.Centre,
@@ -74,31 +70,5 @@ namespace osu.Game.Rulesets.Catch.Tests
LifetimeEnd = double.PositiveInfinity,
};
}
private Color4 colourForRrepesentation(FruitVisualRepresentation representation)
{
switch (representation)
{
default:
case FruitVisualRepresentation.Pear:
return new Color4(17, 136, 170, 255);
case FruitVisualRepresentation.Grape:
return new Color4(204, 102, 0, 255);
case FruitVisualRepresentation.Raspberry:
return new Color4(121, 9, 13, 255);
case FruitVisualRepresentation.Pineapple:
return new Color4(102, 136, 0, 255);
case FruitVisualRepresentation.Banana:
switch (RNG.Next(0, 3))
{
default:
return new Color4(255, 240, 0, 255);
case 1:
return new Color4(255, 192, 0, 255);
case 2:
return new Color4(214, 221, 28, 255);
}
}
}
}
}
@@ -54,7 +54,6 @@ namespace osu.Game.Rulesets.Catch.UI
if (caughtFruit == null) return;
caughtFruit.AccentColour = fruit.AccentColour;
caughtFruit.RelativePositionAxes = Axes.None;
caughtFruit.Position = new Vector2(MovableCatcher.ToLocalSpace(fruit.ScreenSpaceDrawQuad.Centre).X - MovableCatcher.DrawSize.X / 2, 0);
@@ -8,7 +8,6 @@ using osu.Game.Rulesets.Mania.Objects.Drawables.Pieces;
using OpenTK.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Game.Rulesets.Mania.Judgements;
using osu.Framework.Extensions.IEnumerableExtensions;
using osu.Framework.Input.Bindings;
using osu.Game.Rulesets.Scoring;
@@ -24,7 +23,6 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
private readonly GlowPiece glowPiece;
private readonly BodyPiece bodyPiece;
private readonly Container<DrawableHoldNoteTick> tickContainer;
private readonly Container fullHeightContainer;
/// <summary>
@@ -40,6 +38,7 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
public DrawableHoldNote(HoldNote hitObject, ManiaAction action)
: base(hitObject, action)
{
Container<DrawableHoldNoteTick> tickContainer;
RelativeSizeAxes = Axes.X;
InternalChildren = new Drawable[]
@@ -57,7 +56,14 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
Origin = Anchor.TopCentre,
RelativeSizeAxes = Axes.X,
},
tickContainer = new Container<DrawableHoldNoteTick> { RelativeSizeAxes = Axes.Both },
tickContainer = new Container<DrawableHoldNoteTick>
{
RelativeSizeAxes = Axes.Both,
ChildrenEnumerable = HitObject.NestedHitObjects.OfType<HoldNoteTick>().Select(tick => new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime
})
},
head = new DrawableHeadNote(this, action)
{
Anchor = Anchor.TopCentre,
@@ -70,16 +76,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
}
};
foreach (var tick in HitObject.NestedHitObjects.OfType<HoldNoteTick>())
{
var drawableTick = new DrawableHoldNoteTick(tick)
{
HoldStartTime = () => holdStartTime
};
tickContainer.Add(drawableTick);
AddNested(drawableTick);
}
foreach (var tick in tickContainer)
AddNested(tick);
AddNested(head);
AddNested(tail);
@@ -90,12 +88,8 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
get { return base.AccentColour; }
set
{
if (base.AccentColour == value)
return;
base.AccentColour = value;
tickContainer.Children.ForEach(t => t.AccentColour = value);
glowPiece.AccentColour = value;
bodyPiece.AccentColour = value;
head.AccentColour = value;
@@ -2,7 +2,6 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using osu.Framework.Graphics;
using OpenTK.Graphics;
using osu.Game.Rulesets.Objects.Drawables;
namespace osu.Game.Rulesets.Mania.Objects.Drawables
@@ -28,16 +27,5 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
if (action != null)
Action = action.Value;
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
if (base.AccentColour == value)
return;
base.AccentColour = value;
}
}
}
}
@@ -48,13 +48,10 @@ namespace osu.Game.Rulesets.Mania.Objects.Drawables
get { return base.AccentColour; }
set
{
if (base.AccentColour == value)
return;
base.AccentColour = value;
laneGlowPiece.AccentColour = value;
GlowPiece.AccentColour = value;
headPiece.AccentColour = value;
laneGlowPiece.AccentColour = AccentColour;
GlowPiece.AccentColour = AccentColour;
headPiece.AccentColour = AccentColour;
}
}
@@ -13,24 +13,7 @@ namespace osu.Game.Rulesets.Osu.Beatmaps
public override void PostProcess(Beatmap<OsuHitObject> beatmap)
{
applyStacking(beatmap);
if (beatmap.ComboColours.Count == 0)
return;
int comboIndex = 0;
int colourIndex = 0;
foreach (var obj in beatmap.HitObjects)
{
if (obj.NewCombo)
{
comboIndex = 0;
colourIndex = (colourIndex + 1) % beatmap.ComboColours.Count;
}
obj.IndexInCurrentCombo = comboIndex++;
obj.ComboColour = beatmap.ComboColours[colourIndex];
}
base.PostProcess(beatmap);
}
private void applyStacking(Beatmap<OsuHitObject> beatmap)
@@ -8,6 +8,7 @@ using osu.Game.Rulesets.Osu.Objects.Drawables.Pieces;
using OpenTK;
using osu.Game.Rulesets.Osu.Judgements;
using osu.Game.Rulesets.Scoring;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -21,7 +22,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
private readonly NumberPiece number;
private readonly GlowPiece glow;
public DrawableHitCircle(HitCircle h) : base(h)
public DrawableHitCircle(HitCircle h)
: base(h)
{
Origin = Anchor.Centre;
@@ -30,13 +32,9 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
InternalChildren = new Drawable[]
{
glow = new GlowPiece
{
Colour = AccentColour
},
glow = new GlowPiece(),
circle = new CirclePiece
{
Colour = AccentColour,
Hit = () =>
{
if (AllJudged)
@@ -52,15 +50,11 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
},
ring = new RingPiece(),
flash = new FlashPiece(),
explode = new ExplodePiece
{
Colour = AccentColour,
},
explode = new ExplodePiece(),
ApproachCircle = new ApproachCircle
{
Alpha = 0,
Scale = new Vector2(4),
Colour = AccentColour,
}
};
@@ -70,6 +64,19 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
base.AccentColour = value;
explode.Colour = AccentColour;
glow.Colour = AccentColour;
circle.Colour = AccentColour;
ApproachCircle.Colour = AccentColour;
}
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
{
if (!userTriggered)
@@ -5,6 +5,9 @@ using System.ComponentModel;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Framework.Graphics;
using System.Linq;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Skinning;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -15,7 +18,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
protected DrawableOsuHitObject(OsuHitObject hitObject)
: base(hitObject)
{
AccentColour = HitObject.ComboColour;
Alpha = 0;
}
@@ -35,6 +37,14 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
}
}
protected override void SkinChanged(ISkinSource skin, bool allowFallback)
{
base.SkinChanged(skin, allowFallback);
if (HitObject is IHasComboInformation combo)
AccentColour = skin.GetValue<SkinConfiguration, Color4>(s => s.ComboColours.Count > 0 ? s.ComboColours[combo.ComboIndex % s.ComboColours.Count] : (Color4?)null) ?? Color4.White;
}
protected virtual void UpdatePreemptState() => this.FadeIn(HitObject.TimeFadein);
protected virtual void UpdateCurrentState(ArmedState state)
@@ -13,6 +13,7 @@ using osu.Game.Rulesets.Osu.Judgements;
using osu.Framework.Graphics.Primitives;
using osu.Game.Configuration;
using osu.Game.Rulesets.Scoring;
using OpenTK.Graphics;
namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
@@ -41,7 +42,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
Body = new SliderBody(s)
{
AccentColour = AccentColour,
PathWidth = s.Scale * 64,
},
ticks = new Container<DrawableSliderTick> { RelativeSizeAxes = Axes.Both },
@@ -50,7 +50,6 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
{
BypassAutoSizeAxes = Axes.Both,
Scale = new Vector2(s.Scale),
AccentColour = AccentColour,
AlwaysPresent = true,
Alpha = 0
},
@@ -87,6 +86,17 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables
HitObject.PositionChanged += _ => Position = HitObject.StackedPosition;
}
public override Color4 AccentColour
{
get { return base.AccentColour; }
set
{
base.AccentColour = value;
Body.AccentColour = AccentColour;
Ball.AccentColour = AccentColour;
}
}
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
@@ -25,7 +25,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Blending = BlendingMode.Additive,
RelativeSizeAxes = Axes.Both,
Alpha = 0.2f,
}, false);
}, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
{
RelativeSizeAxes = Axes.Both
}
}, false);
}, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}
@@ -29,7 +29,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Texture = textures.Get(name),
Blending = BlendingMode.Additive,
Alpha = 0.5f
}, false);
}, s => s.GetTexture("Play/osu/hitcircle") == null);
}
}
}
@@ -40,7 +40,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
Colour = Color4.White.Opacity(0.5f),
},
Child = new Box()
}, false),
}, s => s.GetTexture("Play/osu/hitcircle") == null),
number = new OsuSpriteText
{
Text = @"1",
@@ -42,7 +42,7 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
public double? SnakedStart { get; private set; }
public double? SnakedEnd { get; private set; }
private Color4 accentColour;
private Color4 accentColour = Color4.White;
/// <summary>
/// Used to colour the path.
/// </summary>
@@ -173,6 +173,8 @@ namespace osu.Game.Rulesets.Osu.Objects.Drawables.Pieces
texture.SetData(upload);
path.Texture = texture;
container.ForceRedraw();
}
private void computeSize()
@@ -6,13 +6,11 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Objects;
using OpenTK;
using osu.Game.Rulesets.Objects.Types;
using OpenTK.Graphics;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets.Edit.Types;
namespace osu.Game.Rulesets.Osu.Objects
{
public abstract class OsuHitObject : HitObject, IHasCombo, IHasEditablePosition
public abstract class OsuHitObject : HitObject, IHasComboInformation, IHasPosition
{
public const double OBJECT_RADIUS = 64;
@@ -53,10 +51,14 @@ namespace osu.Game.Rulesets.Osu.Objects
public float Scale { get; set; } = 1;
public Color4 ComboColour { get; set; } = Color4.Gray;
public virtual bool NewCombo { get; set; }
public int IndexInCurrentCombo { get; set; }
public int ComboIndex { get; set; }
public bool LastInCombo { get; set; }
protected override void ApplyDefaultsToSelf(ControlPointInfo controlPointInfo, BeatmapDifficulty difficulty)
{
base.ApplyDefaultsToSelf(controlPointInfo, difficulty);
+4 -6
View File
@@ -99,10 +99,10 @@ namespace osu.Game.Rulesets.Osu.Objects
{
StartTime = StartTime,
Position = Position,
IndexInCurrentCombo = IndexInCurrentCombo,
ComboColour = ComboColour,
Samples = Samples,
SampleControlPoint = SampleControlPoint
SampleControlPoint = SampleControlPoint,
IndexInCurrentCombo = IndexInCurrentCombo,
ComboIndex = ComboIndex,
};
TailCircle = new SliderCircle(this)
@@ -110,7 +110,7 @@ namespace osu.Game.Rulesets.Osu.Objects
StartTime = EndTime,
Position = EndPosition,
IndexInCurrentCombo = IndexInCurrentCombo,
ComboColour = ComboColour
ComboIndex = ComboIndex,
};
AddNested(HeadCircle);
@@ -160,7 +160,6 @@ namespace osu.Game.Rulesets.Osu.Objects
Position = Position + Curve.PositionAt(distanceProgress),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = sampleList
});
}
@@ -179,7 +178,6 @@ namespace osu.Game.Rulesets.Osu.Objects
Position = Position + Curve.PositionAt(repeat % 2),
StackHeight = StackHeight,
Scale = Scale,
ComboColour = ComboColour,
Samples = new List<SampleInfo>(RepeatSamples[repeatIndex])
});
}
@@ -10,7 +10,6 @@ using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Rulesets.Osu.Objects.Drawables;
using osu.Game.Tests.Visual;
using OpenTK;
using OpenTK.Graphics;
using osu.Game.Rulesets.Osu.Judgements;
using System.Collections.Generic;
using System;
@@ -61,7 +60,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000 + timeOffset,
Position = positionOffset.Value,
ComboColour = Color4.LightSeaGreen
};
circle.ApplyDefaults(new ControlPointInfo(), new BeatmapDifficulty { CircleSize = circleSize });
@@ -117,7 +117,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-(distance / 2), 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@@ -138,7 +137,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@@ -162,7 +160,6 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@@ -189,7 +186,6 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Bezier,
StartTime = Time.Current + 1000,
Position = new Vector2(-200, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@@ -215,7 +211,6 @@ namespace osu.Game.Rulesets.Osu.Tests
CurveType = CurveType.Linear,
StartTime = Time.Current + 1000,
Position = new Vector2(0, 0),
ComboColour = Color4.LightSeaGreen,
ControlPoints = new List<Vector2>
{
Vector2.Zero,
@@ -245,7 +240,6 @@ namespace osu.Game.Rulesets.Osu.Tests
{
StartTime = Time.Current + 1000,
Position = new Vector2(-100, 0),
ComboColour = Color4.LightSeaGreen,
CurveType = CurveType.Catmull,
ControlPoints = new List<Vector2>
{
@@ -11,6 +11,7 @@ using osu.Framework.Graphics.Shapes;
using osu.Framework.Input.Bindings;
using osu.Game.Beatmaps;
using osu.Game.Configuration;
using osu.Game.Skinning;
using OpenTK;
using OpenTK.Graphics;
@@ -82,7 +83,7 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
public class OsuCursor : Container
{
private Container cursorContainer;
private Drawable cursorContainer;
private Bindable<double> cursorScale;
private Bindable<bool> autoCursorScale;
@@ -97,66 +98,66 @@ namespace osu.Game.Rulesets.Osu.UI.Cursor
[BackgroundDependencyLoader]
private void load(OsuConfigManager config, OsuGameBase game)
{
Children = new Drawable[]
Child = cursorContainer = new SkinnableDrawable("cursor", _ => new CircularContainer
{
cursorContainer = new CircularContainer
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = Size.X / 6,
BorderColour = Color4.White,
EdgeEffect = new EdgeEffectParameters
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = Size.X / 6,
BorderColour = Color4.White,
EdgeEffect = new EdgeEffectParameters
{
Type = EdgeEffectType.Shadow,
Colour = Color4.Pink.Opacity(0.5f),
Radius = 5,
},
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
},
new CircularContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = Size.X / 3,
BorderColour = Color4.White.Opacity(0.5f),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
},
},
},
new CircularContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.1f),
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
},
},
}
Type = EdgeEffectType.Shadow,
Colour = Color4.Pink.Opacity(0.5f),
Radius = 5,
},
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
},
new CircularContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Masking = true,
BorderThickness = Size.X / 3,
BorderColour = Color4.White.Opacity(0.5f),
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Alpha = 0,
AlwaysPresent = true,
},
},
},
new CircularContainer
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
Scale = new Vector2(0.1f),
Masking = true,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = Color4.White,
},
},
},
}
}, restrictSize: false)
{
Origin = Anchor.Centre,
Anchor = Anchor.Centre,
RelativeSizeAxes = Axes.Both,
};
beatmap = game.Beatmap.GetBoundCopy();
@@ -20,11 +20,9 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public class DrawableDrumRoll : DrawableTaikoHitObject<DrumRoll>
{
/// <summary>
/// Number of rolling hits required to reach the dark/final accent colour.
/// Number of rolling hits required to reach the dark/final colour.
/// </summary>
private const int rolling_hits_for_dark_accent = 5;
private Color4 accentDarkColour;
private const int rolling_hits_for_engaged_colour = 5;
/// <summary>
/// Rolling number of tick hits. This increases for hits and decreases for misses.
@@ -53,11 +51,14 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
public override bool OnPressed(TaikoAction action) => false;
private Color4 colourIdle;
private Color4 colourEngaged;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
MainPiece.AccentColour = AccentColour = colours.YellowDark;
accentDarkColour = colours.YellowDarker;
MainPiece.AccentColour = colourIdle = colours.YellowDark;
colourEngaged = colours.YellowDarker;
}
private void onTickJudgement(DrawableHitObject obj, Judgement judgement)
@@ -67,10 +68,10 @@ namespace osu.Game.Rulesets.Taiko.Objects.Drawables
else
rollingHits--;
rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_dark_accent);
rollingHits = MathHelper.Clamp(rollingHits, 0, rolling_hits_for_engaged_colour);
Color4 newAccent = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_dark_accent, AccentColour, accentDarkColour, 0, 1);
MainPiece.FadeAccent(newAccent, 100);
Color4 newColour = Interpolation.ValueAt((float)rollingHits / rolling_hits_for_engaged_colour, colourIdle, colourEngaged, 0, 1);
MainPiece.FadeAccent(newColour, 100);
}
protected override void CheckForJudgements(bool userTriggered, double timeOffset)
@@ -11,6 +11,7 @@ using osu.Game.Audio;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Beatmaps.Formats;
using osu.Game.Beatmaps.Timing;
using osu.Game.Skinning;
namespace osu.Game.Tests.Beatmaps.Formats
{
@@ -163,7 +164,7 @@ namespace osu.Game.Tests.Beatmaps.Formats
[Test]
public void TestDecodeBeatmapColors()
{
var decoder = new LegacyBeatmapDecoder();
var decoder = new LegacySkinDecoder();
using (var resStream = Resource.OpenResource("Soleily - Renatus (Gamu) [Insane].osu"))
using (var stream = new StreamReader(resStream))
{
@@ -12,7 +12,6 @@ using osu.Game.IO.Serialization;
using osu.Game.Rulesets.Objects.Types;
using osu.Game.Tests.Resources;
using OpenTK;
using OpenTK.Graphics;
namespace osu.Game.Tests.Beatmaps.Formats
{
@@ -89,24 +88,6 @@ namespace osu.Game.Tests.Beatmaps.Formats
Assert.AreEqual(2, difficulty.SliderTickRate);
}
[Test]
public void TestDecodeColors()
{
var beatmap = decodeAsJson(normal);
Color4[] expected =
{
new Color4(142, 199, 255, 255),
new Color4(255, 128, 128, 255),
new Color4(128, 255, 255, 255),
new Color4(128, 255, 128, 255),
new Color4(255, 187, 255, 255),
new Color4(255, 177, 140, 255),
};
Assert.AreEqual(expected.Length, beatmap.ComboColours.Count);
for (int i = 0; i < expected.Length; i++)
Assert.AreEqual(expected[i], beatmap.ComboColours[i]);
}
[Test]
public void TestDecodeHitObjects()
{
@@ -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;
using System.Collections.Generic;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Screens.Edit.Screens.Compose;
using OpenTK;
namespace osu.Game.Tests.Visual
{
public class TestCaseBeatDivisorControl : OsuTestCase
{
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(BindableBeatDivisor) };
[BackgroundDependencyLoader]
private void load()
{
Child = new BeatDivisorControl(new BindableBeatDivisor())
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(90, 90)
};
}
}
}
+16 -24
View File
@@ -2,47 +2,39 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Game.Beatmaps;
using osu.Framework.Timing;
using osu.Game.Rulesets.Osu;
using osu.Game.Screens.Edit.Screens.Compose;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseEditorCompose : OsuTestCase
{
private readonly Random random;
private readonly Compose compose;
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(Compose) };
public TestCaseEditorCompose()
{
random = new Random(1337);
private DependencyContainer dependencies;
Add(compose = new Compose());
AddStep("Next beatmap", nextBeatmap);
}
private OsuGameBase osuGame;
private BeatmapManager beatmaps;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame, BeatmapManager beatmaps)
private void load(OsuGameBase osuGame)
{
this.osuGame = osuGame;
this.beatmaps = beatmaps;
osuGame.Beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo);
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
dependencies.CacheAs<IAdjustableClock>(clock);
dependencies.CacheAs<IFrameBasedClock>(clock);
var compose = new Compose();
compose.Beatmap.BindTo(osuGame.Beatmap);
}
private void nextBeatmap()
{
var sets = beatmaps.GetAllUsableBeatmapSets();
if (sets.Count == 0)
return;
var b = sets[random.Next(0, sets.Count)].Beatmaps[0];
osuGame.Beatmap.Value = beatmaps.GetWorkingBeatmap(b);
Child = compose;
}
}
}
@@ -10,6 +10,7 @@ using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.Rulesets;
@@ -17,6 +18,7 @@ using osu.Game.Rulesets.Edit;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Osu;
using osu.Game.Rulesets.Osu.Objects;
using osu.Game.Screens.Edit.Screens.Compose;
using osu.Game.Tests.Beatmaps;
using OpenTK;
using OpenTK.Graphics;
@@ -30,9 +32,23 @@ namespace osu.Game.Tests.Visual
private Track track;
private HitObjectComposer composer;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor(4);
private DecoupleableInterpolatingFramedClock clock;
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame)
{
clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
dependencies.CacheAs<IAdjustableClock>(clock);
dependencies.CacheAs<IFrameBasedClock>(clock);
dependencies.Cache(beatDivisor);
var testBeatmap = new Beatmap
{
ControlPointInfo = new ControlPointInfo
@@ -63,7 +79,7 @@ namespace osu.Game.Tests.Visual
Content = new[]
{
new Drawable[] { composer = new TestHitObjectComposer(new OsuRuleset()) },
new Drawable[] { new TimingPointVisualiser(testBeatmap, track) },
new Drawable[] { new TimingPointVisualiser(testBeatmap, track) { Clock = clock } },
},
RowDimensions = new[]
{
@@ -92,17 +108,17 @@ namespace osu.Game.Tests.Visual
// Forwards
AddStep("Seek(0)", () => composer.SeekTo(0));
AddAssert("Time = 0", () => track.CurrentTime == 0);
AddAssert("Time = 0", () => clock.CurrentTime == 0);
AddStep("Seek(33)", () => composer.SeekTo(33));
AddAssert("Time = 33", () => track.CurrentTime == 33);
AddAssert("Time = 33", () => clock.CurrentTime == 33);
AddStep("Seek(89)", () => composer.SeekTo(89));
AddAssert("Time = 89", () => track.CurrentTime == 89);
AddAssert("Time = 89", () => clock.CurrentTime == 89);
// Backwards
AddStep("Seek(25)", () => composer.SeekTo(25));
AddAssert("Time = 25", () => track.CurrentTime == 25);
AddAssert("Time = 25", () => clock.CurrentTime == 25);
AddStep("Seek(0)", () => composer.SeekTo(0));
AddAssert("Time = 0", () => track.CurrentTime == 0);
AddAssert("Time = 0", () => clock.CurrentTime == 0);
}
/// <summary>
@@ -114,19 +130,19 @@ namespace osu.Game.Tests.Visual
reset();
AddStep("Seek(0), Snap", () => composer.SeekTo(0, true));
AddAssert("Time = 0", () => track.CurrentTime == 0);
AddAssert("Time = 0", () => clock.CurrentTime == 0);
AddStep("Seek(50), Snap", () => composer.SeekTo(50, true));
AddAssert("Time = 50", () => track.CurrentTime == 50);
AddAssert("Time = 50", () => clock.CurrentTime == 50);
AddStep("Seek(100), Snap", () => composer.SeekTo(100, true));
AddAssert("Time = 100", () => track.CurrentTime == 100);
AddAssert("Time = 100", () => clock.CurrentTime == 100);
AddStep("Seek(175), Snap", () => composer.SeekTo(175, true));
AddAssert("Time = 175", () => track.CurrentTime == 175);
AddAssert("Time = 175", () => clock.CurrentTime == 175);
AddStep("Seek(350), Snap", () => composer.SeekTo(350, true));
AddAssert("Time = 350", () => track.CurrentTime == 350);
AddAssert("Time = 350", () => clock.CurrentTime == 350);
AddStep("Seek(400), Snap", () => composer.SeekTo(400, true));
AddAssert("Time = 400", () => track.CurrentTime == 400);
AddAssert("Time = 400", () => clock.CurrentTime == 400);
AddStep("Seek(450), Snap", () => composer.SeekTo(450, true));
AddAssert("Time = 450", () => track.CurrentTime == 450);
AddAssert("Time = 450", () => clock.CurrentTime == 450);
}
/// <summary>
@@ -139,17 +155,17 @@ namespace osu.Game.Tests.Visual
reset();
AddStep("Seek(24), Snap", () => composer.SeekTo(24, true));
AddAssert("Time = 0", () => track.CurrentTime == 0);
AddAssert("Time = 0", () => clock.CurrentTime == 0);
AddStep("Seek(26), Snap", () => composer.SeekTo(26, true));
AddAssert("Time = 50", () => track.CurrentTime == 50);
AddAssert("Time = 50", () => clock.CurrentTime == 50);
AddStep("Seek(150), Snap", () => composer.SeekTo(150, true));
AddAssert("Time = 100", () => track.CurrentTime == 100);
AddAssert("Time = 100", () => clock.CurrentTime == 100);
AddStep("Seek(170), Snap", () => composer.SeekTo(170, true));
AddAssert("Time = 175", () => track.CurrentTime == 175);
AddAssert("Time = 175", () => clock.CurrentTime == 175);
AddStep("Seek(274), Snap", () => composer.SeekTo(274, true));
AddAssert("Time = 175", () => track.CurrentTime == 175);
AddAssert("Time = 175", () => clock.CurrentTime == 175);
AddStep("Seek(276), Snap", () => composer.SeekTo(276, true));
AddAssert("Time = 350", () => track.CurrentTime == 350);
AddAssert("Time = 350", () => clock.CurrentTime == 350);
}
/// <summary>
@@ -160,15 +176,15 @@ namespace osu.Game.Tests.Visual
reset();
AddStep("SeekForward", () => composer.SeekForward());
AddAssert("Time = 50", () => track.CurrentTime == 50);
AddAssert("Time = 50", () => clock.CurrentTime == 50);
AddStep("SeekForward", () => composer.SeekForward());
AddAssert("Time = 100", () => track.CurrentTime == 100);
AddAssert("Time = 100", () => clock.CurrentTime == 100);
AddStep("SeekForward", () => composer.SeekForward());
AddAssert("Time = 200", () => track.CurrentTime == 200);
AddAssert("Time = 200", () => clock.CurrentTime == 200);
AddStep("SeekForward", () => composer.SeekForward());
AddAssert("Time = 400", () => track.CurrentTime == 400);
AddAssert("Time = 400", () => clock.CurrentTime == 400);
AddStep("SeekForward", () => composer.SeekForward());
AddAssert("Time = 450", () => track.CurrentTime == 450);
AddAssert("Time = 450", () => clock.CurrentTime == 450);
}
/// <summary>
@@ -179,17 +195,17 @@ namespace osu.Game.Tests.Visual
reset();
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 50", () => track.CurrentTime == 50);
AddAssert("Time = 50", () => clock.CurrentTime == 50);
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 100", () => track.CurrentTime == 100);
AddAssert("Time = 100", () => clock.CurrentTime == 100);
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 175", () => track.CurrentTime == 175);
AddAssert("Time = 175", () => clock.CurrentTime == 175);
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 350", () => track.CurrentTime == 350);
AddAssert("Time = 350", () => clock.CurrentTime == 350);
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 400", () => track.CurrentTime == 400);
AddAssert("Time = 400", () => clock.CurrentTime == 400);
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 450", () => track.CurrentTime == 450);
AddAssert("Time = 450", () => clock.CurrentTime == 450);
}
/// <summary>
@@ -202,28 +218,28 @@ namespace osu.Game.Tests.Visual
AddStep("Seek(49)", () => composer.SeekTo(49));
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 50", () => track.CurrentTime == 50);
AddAssert("Time = 50", () => clock.CurrentTime == 50);
AddStep("Seek(49.999)", () => composer.SeekTo(49.999));
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 50", () => track.CurrentTime == 50);
AddAssert("Time = 50", () => clock.CurrentTime == 50);
AddStep("Seek(99)", () => composer.SeekTo(99));
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 100", () => track.CurrentTime == 100);
AddAssert("Time = 100", () => clock.CurrentTime == 100);
AddStep("Seek(99.999)", () => composer.SeekTo(99.999));
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 100", () => track.CurrentTime == 100);
AddAssert("Time = 100", () => clock.CurrentTime == 100);
AddStep("Seek(174)", () => composer.SeekTo(174));
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 175", () => track.CurrentTime == 175);
AddAssert("Time = 175", () => clock.CurrentTime == 175);
AddStep("Seek(349)", () => composer.SeekTo(349));
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 350", () => track.CurrentTime == 350);
AddAssert("Time = 350", () => clock.CurrentTime == 350);
AddStep("Seek(399)", () => composer.SeekTo(399));
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 400", () => track.CurrentTime == 400);
AddAssert("Time = 400", () => clock.CurrentTime == 400);
AddStep("Seek(449)", () => composer.SeekTo(449));
AddStep("SeekForward, Snap", () => composer.SeekForward(true));
AddAssert("Time = 450", () => track.CurrentTime == 450);
AddAssert("Time = 450", () => clock.CurrentTime == 450);
}
/// <summary>
@@ -235,17 +251,17 @@ namespace osu.Game.Tests.Visual
AddStep("Seek(450)", () => composer.SeekTo(450));
AddStep("SeekBackward", () => composer.SeekBackward());
AddAssert("Time = 425", () => track.CurrentTime == 425);
AddAssert("Time = 425", () => clock.CurrentTime == 425);
AddStep("SeekBackward", () => composer.SeekBackward());
AddAssert("Time = 375", () => track.CurrentTime == 375);
AddAssert("Time = 375", () => clock.CurrentTime == 375);
AddStep("SeekBackward", () => composer.SeekBackward());
AddAssert("Time = 325", () => track.CurrentTime == 325);
AddAssert("Time = 325", () => clock.CurrentTime == 325);
AddStep("SeekBackward", () => composer.SeekBackward());
AddAssert("Time = 125", () => track.CurrentTime == 125);
AddAssert("Time = 125", () => clock.CurrentTime == 125);
AddStep("SeekBackward", () => composer.SeekBackward());
AddAssert("Time = 25", () => track.CurrentTime == 25);
AddAssert("Time = 25", () => clock.CurrentTime == 25);
AddStep("SeekBackward", () => composer.SeekBackward());
AddAssert("Time = 0", () => track.CurrentTime == 0);
AddAssert("Time = 0", () => clock.CurrentTime == 0);
}
/// <summary>
@@ -257,17 +273,17 @@ namespace osu.Game.Tests.Visual
AddStep("Seek(450)", () => composer.SeekTo(450));
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 400", () => track.CurrentTime == 400);
AddAssert("Time = 400", () => clock.CurrentTime == 400);
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 350", () => track.CurrentTime == 350);
AddAssert("Time = 350", () => clock.CurrentTime == 350);
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 175", () => track.CurrentTime == 175);
AddAssert("Time = 175", () => clock.CurrentTime == 175);
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 100", () => track.CurrentTime == 100);
AddAssert("Time = 100", () => clock.CurrentTime == 100);
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 50", () => track.CurrentTime == 50);
AddAssert("Time = 50", () => clock.CurrentTime == 50);
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 0", () => track.CurrentTime == 0);
AddAssert("Time = 0", () => clock.CurrentTime == 0);
}
/// <summary>
@@ -280,16 +296,16 @@ namespace osu.Game.Tests.Visual
AddStep("Seek(451)", () => composer.SeekTo(451));
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 450", () => track.CurrentTime == 450);
AddAssert("Time = 450", () => clock.CurrentTime == 450);
AddStep("Seek(450.999)", () => composer.SeekTo(450.999));
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 450", () => track.CurrentTime == 450);
AddAssert("Time = 450", () => clock.CurrentTime == 450);
AddStep("Seek(401)", () => composer.SeekTo(401));
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 400", () => track.CurrentTime == 400);
AddAssert("Time = 400", () => clock.CurrentTime == 400);
AddStep("Seek(401.999)", () => composer.SeekTo(401.999));
AddStep("SeekBackward, Snap", () => composer.SeekBackward(true));
AddAssert("Time = 400", () => track.CurrentTime == 400);
AddAssert("Time = 400", () => clock.CurrentTime == 400);
}
/// <summary>
@@ -307,23 +323,23 @@ namespace osu.Game.Tests.Visual
{
AddStep("SeekForward, Snap", () =>
{
lastTime = track.CurrentTime;
lastTime = clock.CurrentTime;
composer.SeekForward(true);
});
AddAssert("Time > lastTime", () => track.CurrentTime > lastTime);
AddAssert("Time > lastTime", () => clock.CurrentTime > lastTime);
}
for (int i = 0; i < 20; i++)
{
AddStep("SeekBackward, Snap", () =>
{
lastTime = track.CurrentTime;
lastTime = clock.CurrentTime;
composer.SeekBackward(true);
});
AddAssert("Time < lastTime", () => track.CurrentTime < lastTime);
AddAssert("Time < lastTime", () => clock.CurrentTime < lastTime);
}
AddAssert("Time = 0", () => track.CurrentTime == 0);
AddAssert("Time = 0", () => clock.CurrentTime == 0);
}
private void reset()
@@ -409,7 +425,7 @@ namespace osu.Game.Tests.Visual
{
base.Update();
tracker.X = (float)(track.CurrentTime / track.Length);
tracker.X = (float)(Time.Current / track.Length);
}
private class TimingPointTimeline : CompositeDrawable
@@ -5,6 +5,7 @@ using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Timing;
using OpenTK;
using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit;
@@ -34,6 +35,11 @@ namespace osu.Game.Tests.Visual
typeof(SliderCircleMask)
};
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame)
{
@@ -59,6 +65,10 @@ namespace osu.Game.Tests.Visual
},
});
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
dependencies.CacheAs<IAdjustableClock>(clock);
dependencies.CacheAs<IFrameBasedClock>(clock);
Child = new OsuHitObjectComposer(new OsuRuleset());
}
}
@@ -4,30 +4,38 @@
using System;
using System.Collections.Generic;
using NUnit.Framework;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Game.Beatmaps;
using osu.Game.Beatmaps.ControlPoints;
using OpenTK;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Framework.Configuration;
using osu.Framework.Timing;
using osu.Game.Rulesets.Osu;
using osu.Game.Tests.Beatmaps;
namespace osu.Game.Tests.Visual
{
[TestFixture]
public class TestCaseEditorSummaryTimeline : OsuTestCase
{
private const int length = 60000;
private readonly Random random;
public override IReadOnlyList<Type> RequiredTypes => new[] { typeof(SummaryTimeline) };
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
public TestCaseEditorSummaryTimeline()
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
private void load()
{
random = new Random(1337);
beatmap.Value = new TestWorkingBeatmap(new OsuRuleset().RulesetInfo);
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
dependencies.CacheAs<IAdjustableClock>(clock);
dependencies.CacheAs<IFrameBasedClock>(clock);
SummaryTimeline summaryTimeline;
Add(summaryTimeline = new SummaryTimeline
@@ -38,58 +46,6 @@ namespace osu.Game.Tests.Visual
});
summaryTimeline.Beatmap.BindTo(beatmap);
AddStep("New beatmap", newBeatmap);
newBeatmap();
}
private void newBeatmap()
{
var b = new Beatmap();
for (int i = 0; i < random.Next(1, 10); i++)
b.ControlPointInfo.TimingPoints.Add(new TimingControlPoint { Time = random.Next(0, length) });
for (int i = 0; i < random.Next(1, 5); i++)
b.ControlPointInfo.DifficultyPoints.Add(new DifficultyControlPoint { Time = random.Next(0, length) });
for (int i = 0; i < random.Next(1, 5); i++)
b.ControlPointInfo.EffectPoints.Add(new EffectControlPoint { Time = random.Next(0, length) });
for (int i = 0; i < random.Next(1, 5); i++)
b.ControlPointInfo.SamplePoints.Add(new SampleControlPoint { Time = random.Next(0, length) });
b.BeatmapInfo.Bookmarks = new int[random.Next(10, 30)];
for (int i = 0; i < b.BeatmapInfo.Bookmarks.Length; i++)
b.BeatmapInfo.Bookmarks[i] = random.Next(0, length);
beatmap.Value = new TestWorkingBeatmap(b);
}
private class TestWorkingBeatmap : WorkingBeatmap
{
private readonly Beatmap beatmap;
public TestWorkingBeatmap(Beatmap beatmap)
: base(beatmap.BeatmapInfo)
{
this.beatmap = beatmap;
}
protected override Texture GetBackground() => null;
protected override Beatmap GetBeatmap() => beatmap;
protected override Track GetTrack() => new TestTrack();
private class TestTrack : TrackVirtual
{
public TestTrack()
{
Length = length;
}
}
}
}
}
@@ -68,7 +68,7 @@ namespace osu.Game.Tests.Visual
IDatabaseContextFactory factory = new SingletonContextFactory(new OsuDbContext());
dependencies.Cache(rulesets = new RulesetStore(factory));
dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null)
dependencies.Cache(manager = new BeatmapManager(storage, factory, rulesets, null, null)
{
DefaultBeatmap = defaultBeatmap = game.Beatmap.Default
});
@@ -2,7 +2,9 @@
// Licensed under the MIT Licence - https://raw.githubusercontent.com/ppy/osu/master/LICENCE
using NUnit.Framework;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit.Components;
using osu.Game.Tests.Beatmaps;
@@ -13,17 +15,28 @@ namespace osu.Game.Tests.Visual
[TestFixture]
public class TestCasePlaybackControl : OsuTestCase
{
public TestCasePlaybackControl()
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
private void load()
{
var clock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
dependencies.CacheAs<IAdjustableClock>(clock);
dependencies.CacheAs<IFrameBasedClock>(clock);
var playback = new PlaybackControl
{
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(200,100)
};
playback.Beatmap.Value = new TestWorkingBeatmap(new Beatmap());
Add(playback);
Child = playback;
}
}
}
+1
View File
@@ -116,6 +116,7 @@
<Compile Include="Visual\TestCaseBeatmapOptionsOverlay.cs" />
<Compile Include="Visual\TestCaseBeatmapScoresContainer.cs" />
<Compile Include="Visual\TestCaseBeatmapSetOverlay.cs" />
<Compile Include="Visual\TestCaseBeatDivisorControl.cs" />
<Compile Include="Visual\TestCaseBeatSyncedContainer.cs" />
<Compile Include="Visual\TestCaseBreadcrumbs.cs" />
<Compile Include="Visual\TestCaseBreakOverlay.cs" />
+1 -12
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 OpenTK.Graphics;
using osu.Game.Beatmaps.Timing;
using osu.Game.Rulesets.Objects;
using System.Collections.Generic;
@@ -9,7 +8,6 @@ using System.Linq;
using osu.Game.Beatmaps.ControlPoints;
using osu.Game.IO.Serialization;
using Newtonsoft.Json;
using osu.Game.Beatmaps.Formats;
using osu.Game.IO.Serialization.Converters;
namespace osu.Game.Beatmaps
@@ -17,21 +15,13 @@ namespace osu.Game.Beatmaps
/// <summary>
/// A Beatmap containing converted HitObjects.
/// </summary>
public class Beatmap<T> : IJsonSerializable, IHasComboColours
public class Beatmap<T> : IJsonSerializable
where T : HitObject
{
public BeatmapInfo BeatmapInfo = new BeatmapInfo();
public ControlPointInfo ControlPointInfo = new ControlPointInfo();
public List<BreakPeriod> Breaks = new List<BreakPeriod>();
public List<Color4> ComboColours { get; set; } = new List<Color4>
{
new Color4(17, 136, 170, 255),
new Color4(102, 136, 0, 255),
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255)
};
[JsonIgnore]
public BeatmapMetadata Metadata => BeatmapInfo?.Metadata ?? BeatmapInfo?.BeatmapSet?.Metadata;
@@ -56,7 +46,6 @@ namespace osu.Game.Beatmaps
BeatmapInfo = original?.BeatmapInfo.DeepClone() ?? BeatmapInfo;
ControlPointInfo = original?.ControlPointInfo ?? ControlPointInfo;
Breaks = original?.Breaks ?? Breaks;
ComboColours = original?.ComboColours ?? ComboColours;
HitObjects = original?.HitObjects ?? HitObjects;
if (original == null && Metadata == null)
-1
View File
@@ -57,7 +57,6 @@ namespace osu.Game.Beatmaps
beatmap.ControlPointInfo = original.ControlPointInfo;
beatmap.HitObjects = original.HitObjects.SelectMany(h => convert(h, original)).ToList();
beatmap.Breaks = original.Breaks;
beatmap.ComboColours = original.ComboColours;
return beatmap;
}
+6 -2
View File
@@ -8,6 +8,7 @@ using System.Linq;
using System.Linq.Expressions;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Extensions;
using osu.Framework.Logging;
using osu.Framework.Platform;
@@ -55,6 +56,8 @@ namespace osu.Game.Beatmaps
private readonly APIAccess api;
private readonly AudioManager audioManager;
private readonly List<DownloadBeatmapSetRequest> currentDownloads = new List<DownloadBeatmapSetRequest>();
/// <summary>
@@ -62,7 +65,7 @@ namespace osu.Game.Beatmaps
/// </summary>
public Func<Storage> GetStableStorage { private get; set; }
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, IIpcHost importHost = null)
public BeatmapManager(Storage storage, IDatabaseContextFactory contextFactory, RulesetStore rulesets, APIAccess api, AudioManager audioManager, IIpcHost importHost = null)
: base(storage, contextFactory, new BeatmapStore(contextFactory), importHost)
{
beatmaps = (BeatmapStore)ModelStore;
@@ -71,6 +74,7 @@ namespace osu.Game.Beatmaps
this.rulesets = rulesets;
this.api = api;
this.audioManager = audioManager;
}
protected override void Populate(BeatmapSetInfo model, ArchiveReader archive)
@@ -217,7 +221,7 @@ namespace osu.Game.Beatmaps
if (beatmapInfo.Metadata == null)
beatmapInfo.Metadata = beatmapInfo.BeatmapSet.Metadata;
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo);
WorkingBeatmap working = new BeatmapManagerWorkingBeatmap(Files.Store, beatmapInfo, audioManager);
previous?.TransferTo(working);
@@ -4,12 +4,14 @@
using System;
using System.IO;
using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Track;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Framework.Logging;
using osu.Game.Beatmaps.Formats;
using osu.Game.Graphics.Textures;
using osu.Game.Skinning;
using osu.Game.Storyboards;
namespace osu.Game.Beatmaps
@@ -19,11 +21,13 @@ namespace osu.Game.Beatmaps
protected class BeatmapManagerWorkingBeatmap : WorkingBeatmap
{
private readonly IResourceStore<byte[]> store;
private readonly AudioManager audioManager;
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, BeatmapInfo beatmapInfo)
public BeatmapManagerWorkingBeatmap(IResourceStore<byte[]> store, BeatmapInfo beatmapInfo, AudioManager audioManager)
: base(beatmapInfo)
{
this.store = store;
this.audioManager = audioManager;
}
protected override Beatmap GetBeatmap()
@@ -100,6 +104,22 @@ namespace osu.Game.Beatmaps
return storyboard;
}
protected override Skin GetSkin()
{
Skin skin;
try
{
skin = new LegacyBeatmapSkin(BeatmapInfo, store, audioManager);
}
catch (Exception e)
{
Logger.Error(e, "Skin failed to load");
skin = new DefaultSkin();
}
return skin;
}
}
}
}
+26 -1
View File
@@ -1,7 +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 System.Linq;
using osu.Game.Rulesets.Objects;
using osu.Game.Rulesets.Objects.Types;
namespace osu.Game.Beatmaps
{
@@ -19,6 +21,29 @@ namespace osu.Game.Beatmaps
/// </para>
/// </summary>
/// <param name="beatmap">The Beatmap to process.</param>
public virtual void PostProcess(Beatmap<TObject> beatmap) { }
public virtual void PostProcess(Beatmap<TObject> beatmap)
{
IHasComboInformation lastObj = null;
foreach (var obj in beatmap.HitObjects.OfType<IHasComboInformation>())
{
if (obj.NewCombo)
{
obj.IndexInCurrentCombo = 0;
if (lastObj != null)
{
lastObj.LastInCombo = true;
obj.ComboIndex = lastObj.ComboIndex + 1;
}
}
else if (lastObj != null)
{
obj.IndexInCurrentCombo = lastObj.IndexInCurrentCombo + 1;
obj.ComboIndex = lastObj.ComboIndex;
}
lastObj = obj;
}
}
}
}
@@ -96,7 +96,7 @@ namespace osu.Game.Beatmaps.Formats
private void handleGeneral(string line)
{
var pair = SplitKeyVal(line, ':');
var pair = SplitKeyVal(line);
var metadata = beatmap.BeatmapInfo.Metadata;
switch (pair.Key)
@@ -155,7 +155,7 @@ namespace osu.Game.Beatmaps.Formats
private void handleEditor(string line)
{
var pair = SplitKeyVal(line, ':');
var pair = SplitKeyVal(line);
switch (pair.Key)
{
@@ -179,7 +179,7 @@ namespace osu.Game.Beatmaps.Formats
private void handleMetadata(string line)
{
var pair = SplitKeyVal(line, ':');
var pair = SplitKeyVal(line);
var metadata = beatmap.BeatmapInfo.Metadata;
switch (pair.Key)
@@ -220,7 +220,7 @@ namespace osu.Game.Beatmaps.Formats
private void handleDifficulty(string line)
{
var pair = SplitKeyVal(line, ':');
var pair = SplitKeyVal(line);
var difficulty = beatmap.BeatmapInfo.BaseDifficulty;
switch (pair.Key)
+8 -3
View File
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.IO;
using osu.Framework.Logging;
using OpenTK.Graphics;
namespace osu.Game.Beatmaps.Formats
@@ -31,7 +32,11 @@ namespace osu.Game.Beatmaps.Formats
if (line.StartsWith(@"[") && line.EndsWith(@"]"))
{
if (!Enum.TryParse(line.Substring(1, line.Length - 2), out section))
throw new InvalidDataException($@"Unknown osu section {line}");
{
Logger.Log($"Unknown section \"{line}\" in {beatmap}");
section = Section.None;
}
continue;
}
@@ -55,7 +60,7 @@ namespace osu.Game.Beatmaps.Formats
private void handleColours(T output, string line)
{
var pair = SplitKeyVal(line, ':');
var pair = SplitKeyVal(line);
bool isCombo = pair.Key.StartsWith(@"Combo");
@@ -89,7 +94,7 @@ namespace osu.Game.Beatmaps.Formats
}
}
protected KeyValuePair<string, string> SplitKeyVal(string line, char separator)
protected KeyValuePair<string, string> SplitKeyVal(string line, char separator = ':')
{
var split = line.Trim().Split(new[] { separator }, 2);
+11
View File
@@ -14,6 +14,7 @@ using osu.Framework.IO.File;
using System.IO;
using osu.Game.IO.Serialization;
using System.Diagnostics;
using osu.Game.Skinning;
namespace osu.Game.Beatmaps
{
@@ -40,6 +41,7 @@ namespace osu.Game.Beatmaps
track = new AsyncLazy<Track>(populateTrack);
waveform = new AsyncLazy<Waveform>(populateWaveform);
storyboard = new AsyncLazy<Storyboard>(populateStoryboard);
skin = new AsyncLazy<Skin>(populateSkin);
}
/// <summary>
@@ -56,6 +58,7 @@ namespace osu.Game.Beatmaps
protected abstract Beatmap GetBeatmap();
protected abstract Texture GetBackground();
protected abstract Track GetTrack();
protected virtual Skin GetSkin() => new DefaultSkin();
protected virtual Waveform GetWaveform() => new Waveform();
protected virtual Storyboard GetStoryboard() => new Storyboard { BeatmapInfo = BeatmapInfo };
@@ -109,6 +112,13 @@ namespace osu.Game.Beatmaps
private Storyboard populateStoryboard() => GetStoryboard();
public bool SkinLoaded => skin.IsResultAvailable;
public Skin Skin => skin.Value.Result;
public async Task<Skin> GetSkinAsync() => await skin.Value;
private readonly AsyncLazy<Skin> skin;
private Skin populateSkin() => GetSkin();
public void TransferTo(WorkingBeatmap other)
{
if (track.IsResultAvailable && Track != null && BeatmapInfo.AudioEquals(other.BeatmapInfo))
@@ -123,6 +133,7 @@ namespace osu.Game.Beatmaps
if (BackgroundLoaded) Background?.Dispose();
if (WaveformLoaded) Waveform?.Dispose();
if (StoryboardLoaded) Storyboard?.Dispose();
if (SkinLoaded) Skin?.Dispose();
}
/// <summary>
+4 -1
View File
@@ -82,6 +82,8 @@ namespace osu.Game.Configuration
Set(OsuSetting.ReleaseStream, ReleaseStream.Lazer);
Set(OsuSetting.Version, string.Empty);
Set(OsuSetting.ScreenshotFormat, ScreenshotFormat.Jpg);
}
public OsuConfigManager(Storage storage) : base(storage)
@@ -125,6 +127,7 @@ namespace osu.Game.Configuration
Version,
ShowConvertedBeatmaps,
SpeedChangeVisualisation,
Skin
Skin,
ScreenshotFormat
}
}
@@ -7,7 +7,6 @@ namespace osu.Game.Configuration
{
public enum ScreenshotFormat
{
Bmp = 0, // TODO: Figure out the best way to hide this from the dropdown
[Description("JPG (web-friendly)")]
Jpg = 1,
[Description("PNG (lossless)")]
+18 -9
View File
@@ -13,6 +13,7 @@ using osu.Game.IO;
using osu.Game.IO.Archives;
using osu.Game.IPC;
using osu.Game.Overlays.Notifications;
using SharpCompress.Common;
using FileInfo = osu.Game.IO.FileInfo;
namespace osu.Game.Database
@@ -79,7 +80,6 @@ namespace osu.Game.Database
var notification = new ProgressNotification
{
Text = "Import is initialising...",
CompletionText = "Import successful!",
Progress = 0,
State = ProgressNotificationState.Active,
};
@@ -88,7 +88,8 @@ namespace osu.Game.Database
List<TModel> imported = new List<TModel>();
int i = 0;
int current = 0;
int errors = 0;
foreach (string path in paths)
{
if (notification.State == ProgressNotificationState.Cancelled)
@@ -97,11 +98,11 @@ namespace osu.Game.Database
try
{
notification.Text = $"Importing ({i} of {paths.Length})\n{Path.GetFileName(path)}";
notification.Text = $"Importing ({++current} of {paths.Length})\n{Path.GetFileName(path)}";
using (ArchiveReader reader = getReaderFrom(path))
imported.Add(Import(reader));
notification.Progress = (float)++i / paths.Length;
notification.Progress = (float)current / paths.Length;
// We may or may not want to delete the file depending on where it is stored.
// e.g. reconstructing/repairing database with items from default storage.
@@ -121,9 +122,11 @@ namespace osu.Game.Database
{
e = e.InnerException ?? e;
Logger.Error(e, $@"Could not import ({Path.GetFileName(path)})");
errors++;
}
}
notification.Text = errors > 0 ? $"Import complete with {errors} errors" : "Import successful!";
notification.State = ProgressNotificationState.Completed;
}
@@ -218,9 +221,11 @@ namespace osu.Game.Database
// user requested abort
return;
notification.Text = $"Deleting ({i} of {items.Count})";
notification.Progress = (float)++i / items.Count;
notification.Text = $"Deleting ({++i} of {items.Count})";
Delete(b);
notification.Progress = (float)i / items.Count;
}
}
@@ -254,9 +259,11 @@ namespace osu.Game.Database
// user requested abort
return;
notification.Text = $"Restoring ({i} of {items.Count})";
notification.Progress = (float)++i / items.Count;
notification.Text = $"Restoring ({++i} of {items.Count})";
Undelete(item);
notification.Progress = (float)i / items.Count;
}
}
@@ -331,7 +338,9 @@ namespace osu.Game.Database
{
if (ZipFile.IsZipFile(path))
return new ZipArchiveReader(Files.Storage.GetStream(path), Path.GetFileName(path));
return new LegacyFilesystemReader(path);
if (Directory.Exists(path))
return new LegacyFilesystemReader(path);
throw new InvalidFormatException($"{path} is not a valid archive");
}
}
}
+2
View File
@@ -10,6 +10,8 @@ namespace osu.Game.Database
/// </summary>
/// <typeparam name="TFile">The model representing a file.</typeparam>
public interface IHasFiles<TFile>
where TFile : INamedFileInfo
{
List<TFile> Files { get; set; }
}
+1 -1
View File
@@ -242,7 +242,7 @@ namespace osu.Game.Graphics.Backgrounds
triangle,
colourInfo,
null,
Shared.VertexBatch.Add,
Shared.VertexBatch.AddAction,
Vector2.Divide(localInflationAmount, size));
}
+110
View File
@@ -0,0 +1,110 @@
// 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.Drawing.Imaging;
using System.IO;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
using osu.Framework.Input;
using osu.Framework.Input.Bindings;
using osu.Framework.Platform;
using osu.Game.Configuration;
using osu.Game.Input.Bindings;
using osu.Game.Overlays;
using osu.Game.Overlays.Notifications;
namespace osu.Game.Graphics
{
public class ScreenshotManager : Container, IKeyBindingHandler<GlobalAction>, IHandleGlobalInput
{
private Bindable<ScreenshotFormat> screenshotFormat;
private GameHost host;
private Storage storage;
private NotificationOverlay notificationOverlay;
private SampleChannel shutter;
[BackgroundDependencyLoader]
private void load(GameHost host, OsuConfigManager config, Storage storage, NotificationOverlay notificationOverlay, AudioManager audio)
{
this.host = host;
this.storage = storage.GetStorageForDirectory(@"screenshots");
this.notificationOverlay = notificationOverlay;
screenshotFormat = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat);
shutter = audio.Sample.Get("UI/shutter");
}
public bool OnPressed(GlobalAction action)
{
switch (action)
{
case GlobalAction.TakeScreenshot:
shutter.Play();
TakeScreenshotAsync();
return true;
}
return false;
}
public bool OnReleased(GlobalAction action) => false;
public async void TakeScreenshotAsync()
{
using (var bitmap = await host.TakeScreenshotAsync())
{
var fileName = getFileName();
if (fileName == null) return;
var stream = storage.GetStream(fileName, FileAccess.Write);
switch (screenshotFormat.Value)
{
case ScreenshotFormat.Png:
bitmap.Save(stream, ImageFormat.Png);
break;
case ScreenshotFormat.Jpg:
bitmap.Save(stream, ImageFormat.Jpeg);
break;
default:
throw new ArgumentOutOfRangeException(nameof(screenshotFormat));
}
notificationOverlay.Post(new SimpleNotification
{
Text = $"{fileName} saved!",
Activated = () =>
{
storage.OpenInNativeExplorer();
return true;
}
});
}
}
private string getFileName()
{
var dt = DateTime.Now;
var fileExt = screenshotFormat.ToString().ToLower();
var withoutIndex = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}.{fileExt}";
if (!storage.Exists(withoutIndex))
return withoutIndex;
for (ulong i = 1; i < ulong.MaxValue; i++)
{
var indexedName = $"osu_{dt:yyyy-MM-dd_HH-mm-ss}-{i}.{fileExt}";
if (!storage.Exists(indexedName))
return indexedName;
}
return null;
}
}
}
+1 -1
View File
@@ -988,7 +988,7 @@ namespace osu.Game.Graphics
fa_osu_expert_mania = 0xe028,
// mod icons
fa_osu_mod_perfect = 0xe02d,
fa_osu_mod_perfect = 0xe049,
fa_osu_mod_autopilot = 0xe03a,
fa_osu_mod_auto = 0xe03b,
fa_osu_mod_cinema = 0xe03c,
@@ -30,6 +30,9 @@ namespace osu.Game.Graphics.UserInterface
}
}
// We may not be focused yet, but we need to handle keyboard input to be able to request focus
public override bool HandleKeyboardInput => HoldFocus || base.HandleKeyboardInput;
protected override void OnFocus(InputState state)
{
base.OnFocus(state);
@@ -26,6 +26,8 @@ namespace osu.Game.Input.Bindings
{
new KeyBinding(InputKey.F8, GlobalAction.ToggleChat),
new KeyBinding(InputKey.F9, GlobalAction.ToggleSocial),
new KeyBinding(InputKey.F12,GlobalAction.TakeScreenshot),
new KeyBinding(new[] { InputKey.Control, InputKey.Alt, InputKey.R }, GlobalAction.ResetInputSettings),
new KeyBinding(new[] { InputKey.Control, InputKey.T }, GlobalAction.ToggleToolbar),
new KeyBinding(new[] { InputKey.Control, InputKey.O }, GlobalAction.ToggleSettings),
@@ -72,5 +74,8 @@ namespace osu.Game.Input.Bindings
SkipCutscene,
[Description("Quick Retry (Hold)")]
QuickRetry,
[Description("Take screenshot")]
TakeScreenshot
}
}
+8 -9
View File
@@ -7,6 +7,7 @@ using System.Collections.Generic;
using System.Diagnostics;
using System.Net;
using System.Threading;
using System.Threading.Tasks;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Logging;
@@ -44,8 +45,7 @@ namespace osu.Game.Online.API
protected bool HasLogin => Token != null || !string.IsNullOrEmpty(ProvidedUsername) && !string.IsNullOrEmpty(password);
// ReSharper disable once PrivateFieldCanBeConvertedToLocalVariable (should dispose of this or at very least keep a reference).
private readonly Thread thread;
private readonly CancellationTokenSource cancellationToken = new CancellationTokenSource();
private readonly Logger log;
@@ -59,8 +59,7 @@ namespace osu.Game.Online.API
ProvidedUsername = config.Get<string>(OsuSetting.Username);
Token = config.Get<string>(OsuSetting.Token);
thread = new Thread(run) { IsBackground = true };
thread.Start();
Task.Factory.StartNew(run, cancellationToken.Token, TaskCreationOptions.LongRunning, TaskScheduler.Default);
}
private readonly List<IOnlineComponent> components = new List<IOnlineComponent>();
@@ -93,7 +92,7 @@ namespace osu.Game.Online.API
private void run()
{
while (thread.IsAlive)
while (!cancellationToken.IsCancellationRequested)
{
switch (State)
{
@@ -267,10 +266,7 @@ namespace osu.Game.Online.API
public bool IsLoggedIn => LocalUser.Value.Id > 1;
public void Queue(APIRequest request)
{
queue.Enqueue(request);
}
public void Queue(APIRequest request) => queue.Enqueue(request);
public event StateChangeDelegate OnStateChange;
@@ -312,6 +308,9 @@ namespace osu.Game.Online.API
config.Set(OsuSetting.Token, config.Get<bool>(OsuSetting.SavePassword) ? Token : string.Empty);
config.Save();
flushQueue();
cancellationToken.Cancel();
}
}
+16 -1
View File
@@ -133,7 +133,6 @@ namespace osu.Game
// bind config int to database SkinInfo
configSkin = LocalConfig.GetBindable<int>(OsuSetting.Skin);
SkinManager.CurrentSkinInfo.ValueChanged += s => configSkin.Value = s.ID;
configSkin.ValueChanged += id => SkinManager.CurrentSkinInfo.Value = SkinManager.Query(s => s.ID == id) ?? SkinInfo.Default;
configSkin.TriggerChange();
@@ -240,6 +239,7 @@ namespace osu.Game
loadComponentSingleFile(volume = new VolumeOverlay(), overlayContent.Add);
loadComponentSingleFile(onscreenDisplay = new OnScreenDisplay(), Add);
loadComponentSingleFile(new ScreenshotManager(), Add);
//overlay elements
loadComponentSingleFile(direct = new DirectOverlay { Depth = -1 }, mainContent.Add);
@@ -302,6 +302,21 @@ namespace osu.Game
};
}
var singleDisplaySideOverlays = new OverlayContainer[] { settings, notifications };
foreach (var overlay in singleDisplaySideOverlays)
{
overlay.StateChanged += state =>
{
if (state == Visibility.Hidden) return;
foreach (var c in singleDisplaySideOverlays)
{
if (c == overlay) continue;
c.State = Visibility.Hidden;
}
};
}
// eventually informational overlays should be displayed in a stack, but for now let's only allow one to stay open at a time.
var informationalOverlays = new OverlayContainer[] { beatmapSetOverlay, userProfile };
foreach (var overlay in informationalOverlays)
+2 -1
View File
@@ -105,6 +105,7 @@ namespace osu.Game
runMigrations();
dependencies.Cache(SkinManager = new SkinManager(Host.Storage, contextFactory, Host, Audio));
dependencies.CacheAs<ISkinSource>(SkinManager);
var api = new APIAccess(LocalConfig);
@@ -113,7 +114,7 @@ namespace osu.Game
dependencies.Cache(RulesetStore = new RulesetStore(contextFactory));
dependencies.Cache(FileStore = new FileStore(contextFactory, Host.Storage));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Host));
dependencies.Cache(BeatmapManager = new BeatmapManager(Host.Storage, contextFactory, RulesetStore, api, Audio, Host));
dependencies.Cache(ScoreStore = new ScoreStore(Host.Storage, contextFactory, Host, BeatmapManager, RulesetStore));
dependencies.Cache(KeyBindingStore = new KeyBindingStore(contextFactory, RulesetStore));
dependencies.Cache(SettingsStore = new SettingsStore(contextFactory));
-12
View File
@@ -4,9 +4,6 @@
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
using osu.Framework.Allocation;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Sprites;
@@ -30,7 +27,6 @@ namespace osu.Game.Overlays.Mods
private ModIcon backgroundIcon;
private readonly SpriteText text;
private readonly Container<ModIcon> iconsContainer;
private SampleChannel sampleOn, sampleOff;
/// <summary>
/// Fired when the selection changes.
@@ -100,7 +96,6 @@ namespace osu.Game.Overlays.Mods
foregroundIcon.Highlighted = Selected;
(selectedIndex == -1 ? sampleOff : sampleOn).Play();
SelectionChanged?.Invoke(SelectedMod);
return true;
}
@@ -152,13 +147,6 @@ namespace osu.Game.Overlays.Mods
public virtual Mod SelectedMod => Mods.ElementAtOrDefault(selectedIndex);
[BackgroundDependencyLoader]
private void load(AudioManager audio)
{
sampleOn = audio.Sample.Get(@"UI/check-on");
sampleOff = audio.Sample.Get(@"UI/check-off");
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
switch (args.Button)
+17 -1
View File
@@ -15,6 +15,8 @@ using osu.Game.Rulesets.Mods;
using System;
using System.Collections.Generic;
using System.Linq;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics.Shapes;
using osu.Game.Rulesets;
using osu.Game.Graphics.UserInterface;
@@ -49,7 +51,7 @@ namespace osu.Game.Overlays.Mods
}
[BackgroundDependencyLoader(permitNulls: true)]
private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets)
private void load(OsuColour colours, OsuGame osu, RulesetStore rulesets, AudioManager audio)
{
SelectedMods.ValueChanged += selectedModsChanged;
@@ -63,6 +65,9 @@ namespace osu.Game.Overlays.Mods
Ruleset.ValueChanged += rulesetChanged;
Ruleset.TriggerChange();
sampleOn = audio.Sample.Get(@"UI/check-on");
sampleOff = audio.Sample.Get(@"UI/check-off");
}
protected override void Dispose(bool isDisposing)
@@ -154,10 +159,21 @@ namespace osu.Game.Overlays.Mods
section.DeselectTypes(modTypes, immediate);
}
private SampleChannel sampleOn, sampleOff;
private void modButtonPressed(Mod selectedMod)
{
if (selectedMod != null)
{
if (State == Visibility.Visible) sampleOn?.Play();
DeselectTypes(selectedMod.IncompatibleMods, true);
}
else
{
if (State == Visibility.Visible) sampleOff?.Play();
}
refreshSelectedMods();
}
+3 -11
View File
@@ -110,17 +110,7 @@ namespace osu.Game.Overlays
private int runningDepth;
private void notificationClosed()
{
Schedule(() =>
{
// hide ourselves if all notifications have been dismissed.
if (totalCount == 0)
State = Visibility.Hidden;
});
updateCounts();
}
private void notificationClosed() => updateCounts();
private readonly Scheduler postScheduler = new Scheduler();
@@ -141,6 +131,8 @@ namespace osu.Game.Overlays
var section = sections.Children.FirstOrDefault(s => s.AcceptTypes.Any(accept => accept.IsAssignableFrom(ourType)));
section?.Add(notification, notification.DisplayOnTop ? -runningDepth : runningDepth);
State = Visibility.Visible;
updateCounts();
});
@@ -1,6 +1,8 @@
// 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.Game.Configuration;
namespace osu.Game.Overlays.Settings.Sections.Graphics
@@ -12,7 +14,7 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
[BackgroundDependencyLoader]
private void load(OsuConfigManager config)
{
Children = new[]
Children = new Drawable[]
{
new SettingsCheckbox
{
@@ -24,6 +26,11 @@ namespace osu.Game.Overlays.Settings.Sections.Graphics
LabelText = "Rotate cursor when dragging",
Bindable = config.GetBindable<bool>(OsuSetting.CursorRotation)
},
new SettingsEnumDropdown<ScreenshotFormat>
{
LabelText = "Screenshot format",
Bindable = config.GetBindable<ScreenshotFormat>(OsuSetting.ScreenshotFormat)
}
};
}
}
@@ -47,7 +47,7 @@ namespace osu.Game.Overlays.Settings.Sections
},
};
void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair<string, int>(s.Name, s.ID));
void reloadSkins() => skinDropdown.Items = skins.GetAllUsableSkins().Select(s => new KeyValuePair<string, int>(s.ToString(), s.ID));
skins.ItemAdded += _ => reloadSkins();
skins.ItemRemoved += _ => reloadSkins();
+17 -10
View File
@@ -12,6 +12,7 @@ using osu.Framework.Graphics.Effects;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input.Bindings;
using osu.Framework.MathUtils;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Input.Bindings;
@@ -93,19 +94,25 @@ namespace osu.Game.Overlays.Volume
Colour = colours.Gray2,
Size = new Vector2(0.8f)
},
(volumeCircle = new CircularProgress
new Container
{
RelativeSizeAxes = Axes.Both,
InnerRadius = 0.05f,
Rotation = 180,
Anchor = Anchor.Centre,
Origin = Anchor.Centre,
Size = new Vector2(0.8f)
}).WithEffect(new GlowEffect
{
Colour = meterColour,
Strength = 2
}),
RelativeSizeAxes = Axes.Both,
Size = new Vector2(0.8f),
Padding = new MarginPadding(-Blur.KernelSize(5)),
Rotation = 180,
Child = (volumeCircle = new CircularProgress
{
RelativeSizeAxes = Axes.Both,
InnerRadius = 0.05f,
}).WithEffect(new GlowEffect
{
Colour = meterColour,
Strength = 2,
PadExtent = true
}),
},
maxGlow = (text = new OsuSpriteText
{
Anchor = Anchor.Centre,
+15 -20
View File
@@ -4,6 +4,7 @@
using System;
using System.Collections.Generic;
using System.Linq;
using JetBrains.Annotations;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
@@ -16,6 +17,7 @@ using osu.Game.Beatmaps;
using osu.Game.Rulesets.Edit.Tools;
using osu.Game.Rulesets.Objects.Drawables;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Edit.Screens.Compose;
using osu.Game.Screens.Edit.Screens.Compose.Layers;
using osu.Game.Screens.Edit.Screens.Compose.RadioButtons;
@@ -31,32 +33,31 @@ namespace osu.Game.Rulesets.Edit
private readonly List<Container> layerContainers = new List<Container>();
private readonly Bindable<WorkingBeatmap> beatmap = new Bindable<WorkingBeatmap>();
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
private IAdjustableClock sourceClock;
private DecoupleableInterpolatingFramedClock adjustableClock;
private IAdjustableClock adjustableClock;
protected HitObjectComposer(Ruleset ruleset)
{
this.ruleset = ruleset;
RelativeSizeAxes = Axes.Both;
}
[BackgroundDependencyLoader]
private void load(OsuGameBase osuGame)
[BackgroundDependencyLoader(true)]
private void load([NotNull] OsuGameBase osuGame, [NotNull] IAdjustableClock adjustableClock, [NotNull] IFrameBasedClock framedClock, [CanBeNull] BindableBeatDivisor beatDivisor)
{
this.adjustableClock = adjustableClock;
if (beatDivisor != null)
this.beatDivisor.BindTo(beatDivisor);
beatmap.BindTo(osuGame.Beatmap);
try
{
rulesetContainer = CreateRulesetContainer(ruleset, beatmap.Value);
// TODO: should probably be done at a RulesetContainer level to share logic with Player.
sourceClock = (IAdjustableClock)beatmap.Value.Track ?? new StopwatchClock();
adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
adjustableClock.ChangeSource(sourceClock);
rulesetContainer.Clock = adjustableClock;
rulesetContainer.Clock = framedClock;
}
catch (Exception e)
{
@@ -172,9 +173,6 @@ namespace osu.Game.Rulesets.Edit
private void seek(int direction, bool snapped)
{
// Todo: This should not be a constant, but feels good for now
const int beat_snap_divisor = 4;
var cpi = beatmap.Value.Beatmap.ControlPointInfo;
var timingPoint = cpi.TimingPointAt(adjustableClock.CurrentTime);
@@ -186,7 +184,7 @@ namespace osu.Game.Rulesets.Edit
timingPoint = cpi.TimingPoints[--activeIndex];
}
double seekAmount = timingPoint.BeatLength / beat_snap_divisor;
double seekAmount = timingPoint.BeatLength / beatDivisor;
double seekTime = adjustableClock.CurrentTime + seekAmount * direction;
if (!snapped || cpi.TimingPoints.Count == 0)
@@ -227,9 +225,6 @@ namespace osu.Game.Rulesets.Edit
public void SeekTo(double seekTime, bool snapped = false)
{
// Todo: This should not be a constant, but feels good for now
const int beat_snap_divisor = 4;
if (!snapped)
{
adjustableClock.Seek(seekTime);
@@ -237,7 +232,7 @@ namespace osu.Game.Rulesets.Edit
}
var timingPoint = beatmap.Value.Beatmap.ControlPointInfo.TimingPointAt(seekTime);
double beatSnapLength = timingPoint.BeatLength / beat_snap_divisor;
double beatSnapLength = timingPoint.BeatLength / beatDivisor;
// We will be snapping to beats within the timing point
seekTime -= timingPoint.Time;
+1 -1
View File
@@ -10,7 +10,7 @@ namespace osu.Game.Rulesets.Mods
{
public override string Name => "Perfect";
public override string ShortenedName => "PF";
public override FontAwesome Icon => FontAwesome.fa_question;
public override FontAwesome Icon => FontAwesome.fa_osu_mod_perfect;
public override string Description => "SS or quit.";
protected override bool FailCondition(ScoreProcessor scoreProcessor) => scoreProcessor.Accuracy.Value != 1;
@@ -6,7 +6,6 @@ using System.Collections.Generic;
using System.Linq;
using osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Primitives;
using osu.Game.Audio;
using osu.Game.Graphics;
@@ -19,7 +18,7 @@ using OpenTK.Graphics;
namespace osu.Game.Rulesets.Objects.Drawables
{
public abstract class DrawableHitObject : CompositeDrawable, IHasAccentColour
public abstract class DrawableHitObject : SkinReloadableDrawable, IHasAccentColour
{
public readonly HitObject HitObject;
@@ -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
namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
/// A HitObject that is part of a combo and has extended information about its position relative to other combo objects.
/// </summary>
public interface IHasComboIndex : IHasCombo
{
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int IndexInCurrentCombo { get; set; }
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int ComboIndex { get; set; }
/// <summary>
/// Whether this is the last object in the current combo.
/// </summary>
bool LastInCombo { get; set; }
}
}
@@ -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
namespace osu.Game.Rulesets.Objects.Types
{
/// <summary>
/// A HitObject that is part of a combo and has extended information about its position relative to other combo objects.
/// </summary>
public interface IHasComboInformation : IHasCombo
{
/// <summary>
/// The offset of this hitobject in the current combo.
/// </summary>
int IndexInCurrentCombo { get; set; }
/// <summary>
/// The offset of this combo in relation to the beatmap.
/// </summary>
int ComboIndex { get; set; }
/// <summary>
/// Whether this is the last object in the current combo.
/// </summary>
bool LastInCombo { get; set; }
}
}
@@ -9,6 +9,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Graphics.Sprites;
using osu.Game.Graphics.UserInterface;
@@ -17,10 +18,15 @@ namespace osu.Game.Screens.Edit.Components
{
public class PlaybackControl : BottomBarContainer
{
private readonly IconButton playButton;
private IconButton playButton;
public PlaybackControl()
private IAdjustableClock adjustableClock;
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock)
{
this.adjustableClock = adjustableClock;
PlaybackTabControl tabs;
Children = new Drawable[]
@@ -54,22 +60,22 @@ namespace osu.Game.Screens.Edit.Components
}
};
tabs.Current.ValueChanged += newValue => Track.Tempo.Value = newValue;
tabs.Current.ValueChanged += newValue => Beatmap.Value.Track.Tempo.Value = newValue;
}
private void togglePause()
{
if (Track.IsRunning)
Track.Stop();
if (adjustableClock.IsRunning)
adjustableClock.Stop();
else
Track.Start();
adjustableClock.Start();
}
protected override void Update()
{
base.Update();
playButton.Icon = Track.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
playButton.Icon = adjustableClock.IsRunning ? FontAwesome.fa_pause_circle_o : FontAwesome.fa_play_circle_o;
}
private class PlaybackTabControl : OsuTabControl<double>
@@ -4,17 +4,20 @@
using osu.Framework.Graphics;
using osu.Game.Graphics.Sprites;
using System;
using osu.Framework.Allocation;
using osu.Framework.Timing;
namespace osu.Game.Screens.Edit.Components
{
public class TimeInfoContainer : BottomBarContainer
{
private const int count_duration = 150;
private readonly OsuSpriteText trackTimer;
private IAdjustableClock adjustableClock;
public TimeInfoContainer()
{
Children = new Drawable[]
{
trackTimer = new OsuSpriteText
@@ -28,11 +31,17 @@ namespace osu.Game.Screens.Edit.Components
};
}
[BackgroundDependencyLoader]
private void load(IAdjustableClock adjustableClock)
{
this.adjustableClock = adjustableClock;
}
protected override void Update()
{
base.Update();
trackTimer.Text = TimeSpan.FromMilliseconds(Track.CurrentTime).ToString(@"mm\:ss\:fff");
trackTimer.Text = TimeSpan.FromMilliseconds(adjustableClock.CurrentTime).ToString(@"mm\:ss\:fff");
}
}
}
@@ -7,6 +7,7 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Input;
using osu.Framework.Timing;
using osu.Game.Beatmaps;
using osu.Game.Graphics;
@@ -19,8 +20,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
{
private readonly Drawable marker;
public MarkerPart()
private readonly IAdjustableClock adjustableClock;
public MarkerPart(IAdjustableClock adjustableClock)
{
this.adjustableClock = adjustableClock;
Add(marker = new MarkerVisualisation());
}
@@ -53,12 +58,12 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary.Parts
seekTo(markerPos / DrawWidth * Beatmap.Value.Track.Length);
}
private void seekTo(double time) => Beatmap.Value?.Track.Seek(time);
private void seekTo(double time) => adjustableClock.Seek(time);
protected override void Update()
{
base.Update();
marker.X = (float)(Beatmap.Value?.Track.CurrentTime ?? 0);
marker.X = (float)adjustableClock.CurrentTime;
}
protected override void LoadBeatmap(WorkingBeatmap beatmap)
@@ -6,6 +6,7 @@ using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Timing;
using osu.Game.Graphics;
using osu.Game.Screens.Edit.Components.Timelines.Summary.Parts;
@@ -16,15 +17,14 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
/// </summary>
public class SummaryTimeline : BottomBarContainer
{
private readonly Drawable timelineBar;
public SummaryTimeline()
[BackgroundDependencyLoader]
private void load(OsuColour colours, IAdjustableClock adjustableClock)
{
TimelinePart markerPart, controlPointPart, bookmarkPart, breakPart;
Children = new[]
Children = new Drawable[]
{
markerPart = new MarkerPart { RelativeSizeAxes = Axes.Both },
markerPart = new MarkerPart(adjustableClock) { RelativeSizeAxes = Axes.Both },
controlPointPart = new ControlPointPart
{
Anchor = Anchor.Centre,
@@ -39,9 +39,10 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
RelativeSizeAxes = Axes.Both,
Height = 0.35f
},
timelineBar = new Container
new Container
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray5,
Children = new Drawable[]
{
new Circle
@@ -80,11 +81,5 @@ namespace osu.Game.Screens.Edit.Components.Timelines.Summary
bookmarkPart.Beatmap.BindTo(Beatmap);
breakPart.Beatmap.BindTo(Beatmap);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
timelineBar.Colour = colours.Gray5;
}
}
}
+19 -7
View File
@@ -12,6 +12,7 @@ using osu.Game.Screens.Edit.Menus;
using osu.Game.Screens.Edit.Components.Timelines.Summary;
using osu.Framework.Allocation;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Timing;
using osu.Game.Graphics.UserInterface;
using osu.Game.Screens.Edit.Screens;
using osu.Game.Screens.Edit.Screens.Compose;
@@ -26,13 +27,27 @@ namespace osu.Game.Screens.Edit
public override bool ShowOverlaysOnEnter => false;
private readonly Box bottomBackground;
private readonly Container screenContainer;
private Box bottomBackground;
private Container screenContainer;
private EditorScreen currentScreen;
public Editor()
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
// TODO: should probably be done at a RulesetContainer level to share logic with Player.
var sourceClock = (IAdjustableClock)Beatmap.Value.Track ?? new StopwatchClock();
var adjustableClock = new DecoupleableInterpolatingFramedClock { IsCoupled = false };
adjustableClock.ChangeSource(sourceClock);
dependencies.CacheAs<IAdjustableClock>(adjustableClock);
dependencies.CacheAs<IFrameBasedClock>(adjustableClock);
EditorMenuBar menuBar;
TimeInfoContainer timeInfo;
SummaryTimeline timeline;
@@ -130,12 +145,9 @@ namespace osu.Game.Screens.Edit
timeline.Beatmap.BindTo(Beatmap);
playback.Beatmap.BindTo(Beatmap);
menuBar.Mode.ValueChanged += onModeChanged;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
bottomBackground.Colour = colours.Gray2;
}
private void exportBeatmap()
@@ -0,0 +1,398 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Colour;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.UserInterface;
using osu.Framework.Input;
using osu.Game.Graphics;
using osu.Game.Graphics.UserInterface;
using OpenTK;
using OpenTK.Graphics;
using OpenTK.Input;
namespace osu.Game.Screens.Edit.Screens.Compose
{
public class BeatDivisorControl : CompositeDrawable
{
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
private int currentDivisorIndex;
public BeatDivisorControl(BindableBeatDivisor beatDivisor)
{
this.beatDivisor.BindTo(beatDivisor);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Masking = true;
CornerRadius = 5;
InternalChildren = new Drawable[]
{
new Box
{
Name = "Gray Background",
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray4
},
new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
Name = "Black Background",
RelativeSizeAxes = Axes.Both,
Colour = Color4.Black
},
new TickSliderBar(beatDivisor, BindableBeatDivisor.VALID_DIVISORS)
{
RelativeSizeAxes = Axes.Both,
}
}
}
},
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Children = new Drawable[]
{
new Box
{
RelativeSizeAxes = Axes.Both,
Colour = colours.Gray4
},
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = 5 },
Child = new GridContainer
{
RelativeSizeAxes = Axes.Both,
Content = new[]
{
new Drawable[]
{
new DivisorButton
{
Icon = FontAwesome.fa_chevron_left,
Action = beatDivisor.Previous
},
new DivisorText(beatDivisor),
new DivisorButton
{
Icon = FontAwesome.fa_chevron_right,
Action = beatDivisor.Next
}
},
},
ColumnDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 20),
new Dimension(),
new Dimension(GridSizeMode.Absolute, 20)
}
}
}
}
}
},
new Drawable[]
{
new TextFlowContainer(s => s.TextSize = 14)
{
Padding = new MarginPadding { Horizontal = 15 },
Text = "beat snap divisor",
RelativeSizeAxes = Axes.X,
TextAnchor = Anchor.TopCentre
},
}
},
RowDimensions = new[]
{
new Dimension(GridSizeMode.Absolute, 30),
new Dimension(GridSizeMode.Absolute, 25),
}
}
};
}
private class DivisorText : SpriteText
{
private readonly Bindable<int> beatDivisor = new Bindable<int>();
public DivisorText(BindableBeatDivisor beatDivisor)
{
this.beatDivisor.BindTo(beatDivisor);
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = colours.BlueLighter;
}
protected override void LoadComplete()
{
base.LoadComplete();
beatDivisor.ValueChanged += v => updateText();
updateText();
}
private void updateText() => Text = $"1/{beatDivisor.Value}";
}
private class DivisorButton : IconButton
{
public DivisorButton()
{
Anchor = Anchor.Centre;
Origin = Anchor.Centre;
// Small offset to look a bit better centered along with the divisor text
Y = 1;
ButtonSize = new Vector2(20);
IconScale = new Vector2(0.6f);
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
IconColour = Color4.Black;
HoverColour = colours.Gray7;
FlashColour = colours.Gray9;
}
}
private class TickSliderBar : SliderBar<int>
{
private Marker marker;
private readonly BindableBeatDivisor beatDivisor;
private readonly int[] availableDivisors;
public TickSliderBar(BindableBeatDivisor beatDivisor, params int[] divisors)
{
CurrentNumber.BindTo(this.beatDivisor = beatDivisor);
availableDivisors = divisors;
Padding = new MarginPadding { Horizontal = 5 };
}
[BackgroundDependencyLoader]
private void load()
{
foreach (var t in availableDivisors)
{
AddInternal(new Tick(t)
{
Anchor = Anchor.TopLeft,
Origin = Anchor.TopCentre,
RelativePositionAxes = Axes.X,
X = getMappedPosition(t)
});
}
AddInternal(marker = new Marker());
CurrentNumber.ValueChanged += v =>
{
marker.MoveToX(getMappedPosition(v), 100, Easing.OutQuint);
marker.Flash();
};
}
protected override void UpdateValue(float value)
{
}
public override bool HandleKeyboardInput => IsHovered && !CurrentNumber.Disabled;
protected override bool OnKeyDown(InputState state, KeyDownEventArgs args)
{
switch (args.Key)
{
case Key.Right:
beatDivisor.Next();
OnUserChange();
return true;
case Key.Left:
beatDivisor.Previous();
OnUserChange();
return true;
default:
return false;
}
}
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
marker.Active = true;
return base.OnMouseDown(state, args);
}
protected override bool OnMouseUp(InputState state, MouseUpEventArgs args)
{
marker.Active = false;
return base.OnMouseUp(state, args);
}
protected override bool OnClick(InputState state)
{
handleMouseInput(state);
return true;
}
protected override bool OnDrag(InputState state)
{
handleMouseInput(state);
return true;
}
private void handleMouseInput(InputState state)
{
// copied from SliderBar so we can do custom spacing logic.
var xPosition = (ToLocalSpace(state?.Mouse.NativeState.Position ?? Vector2.Zero).X - RangePadding) / UsableWidth;
CurrentNumber.Value = availableDivisors.OrderBy(d => Math.Abs(getMappedPosition(d) - xPosition)).First();
OnUserChange();
}
private float getMappedPosition(float divisor) => (float)Math.Pow((divisor - 1) / (availableDivisors.Last() - 1), 0.90f);
private class Tick : CompositeDrawable
{
private readonly int divisor;
public Tick(int divisor)
{
this.divisor = divisor;
Size = new Vector2(2.5f, 10);
InternalChild = new Box { RelativeSizeAxes = Axes.Both };
CornerRadius = 0.5f;
Masking = true;
}
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = getColourForDivisor(divisor, colours);
}
private ColourInfo getColourForDivisor(int divisor, OsuColour colours)
{
switch (divisor)
{
case 2:
return colours.BlueLight;
case 4:
return colours.Blue;
case 8:
return colours.BlueDarker;
case 16:
return colours.PurpleDark;
case 3:
return colours.YellowLight;
case 6:
return colours.Yellow;
case 12:
return colours.YellowDarker;
default:
return Color4.White;
}
}
}
private class Marker : CompositeDrawable
{
private Color4 defaultColour;
private const float size = 7;
[BackgroundDependencyLoader]
private void load(OsuColour colours)
{
Colour = defaultColour = colours.Gray4;
Anchor = Anchor.TopLeft;
Origin = Anchor.TopCentre;
Width = size;
RelativeSizeAxes = Axes.Y;
RelativePositionAxes = Axes.X;
InternalChildren = new Drawable[]
{
new Box
{
Width = 2,
RelativeSizeAxes = Axes.Y,
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Colour = ColourInfo.GradientVertical(Color4.White.Opacity(0.2f), Color4.White),
Blending = BlendingMode.Additive,
},
new EquilateralTriangle
{
Origin = Anchor.BottomCentre,
Anchor = Anchor.BottomCentre,
Height = size,
EdgeSmoothness = new Vector2(1),
Colour = Color4.White,
}
};
}
private bool active;
public bool Active
{
get => active;
set
{
this.FadeColour(value ? Color4.White : defaultColour, 500, Easing.OutQuint);
active = value;
}
}
public void Flash()
{
bool wasActive = active;
Active = true;
if (wasActive) return;
using (BeginDelayedSequence(50))
Active = false;
}
}
}
}
}
@@ -0,0 +1,39 @@
// 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 osu.Framework.Configuration;
namespace osu.Game.Screens.Edit.Screens.Compose
{
public class BindableBeatDivisor : BindableNumber<int>
{
public static readonly int[] VALID_DIVISORS = { 1, 2, 3, 4, 6, 8, 12, 16 };
public BindableBeatDivisor(int value = 1)
: base(value)
{
}
public void Next() => Value = VALID_DIVISORS[Math.Min(VALID_DIVISORS.Length - 1, Array.IndexOf(VALID_DIVISORS, Value) + 1)];
public void Previous() => Value = VALID_DIVISORS[Math.Max(0, Array.IndexOf(VALID_DIVISORS, Value) - 1)];
public override int Value
{
get { return base.Value; }
set
{
if (!VALID_DIVISORS.Contains(value))
throw new ArgumentOutOfRangeException($"Provided divisor is not in {nameof(VALID_DIVISORS)}");
base.Value = value;
}
}
protected override int DefaultMinValue => VALID_DIVISORS.First();
protected override int DefaultMaxValue => VALID_DIVISORS.Last();
protected override int DefaultPrecision => 1;
}
}
@@ -1,13 +1,13 @@
// 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 OpenTK.Graphics;
using osu.Framework.Extensions.Color4Extensions;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Shapes;
using osu.Framework.Logging;
using osu.Game.Beatmaps;
using osu.Game.Screens.Edit.Screens.Compose.Timeline;
namespace osu.Game.Screens.Edit.Screens.Compose
@@ -17,10 +17,20 @@ namespace osu.Game.Screens.Edit.Screens.Compose
private const float vertical_margins = 10;
private const float horizontal_margins = 20;
private readonly Container composerContainer;
private readonly BindableBeatDivisor beatDivisor = new BindableBeatDivisor();
public Compose()
private Container composerContainer;
private DependencyContainer dependencies;
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
=> dependencies = new DependencyContainer(parent);
[BackgroundDependencyLoader]
private void load()
{
dependencies.Cache(beatDivisor);
ScrollableTimeline timeline;
Children = new Drawable[]
{
@@ -47,15 +57,28 @@ namespace osu.Game.Screens.Edit.Screens.Compose
Name = "Timeline content",
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Horizontal = horizontal_margins, Vertical = vertical_margins },
Children = new Drawable[]
Child = new GridContainer
{
new Container
RelativeSizeAxes = Axes.Both,
Content = new[]
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 115 },
Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
new Drawable[]
{
new Container
{
RelativeSizeAxes = Axes.Both,
Padding = new MarginPadding { Right = 5 },
Child = timeline = new ScrollableTimeline { RelativeSizeAxes = Axes.Both }
},
new BeatDivisorControl(beatDivisor) { RelativeSizeAxes = Axes.Both }
},
},
ColumnDimensions = new[]
{
new Dimension(),
new Dimension(GridSizeMode.Absolute, 90),
}
}
},
}
}
}
@@ -75,14 +98,8 @@ namespace osu.Game.Screens.Edit.Screens.Compose
};
timeline.Beatmap.BindTo(Beatmap);
Beatmap.ValueChanged += beatmapChanged;
}
private void beatmapChanged(WorkingBeatmap newBeatmap)
{
composerContainer.Clear();
var ruleset = newBeatmap.BeatmapInfo.Ruleset?.CreateInstance();
var ruleset = Beatmap.Value.BeatmapInfo.Ruleset?.CreateInstance();
if (ruleset == null)
{
Logger.Log("Beatmap doesn't have a ruleset assigned.");
@@ -51,7 +51,11 @@ namespace osu.Game.Screens.Edit.Screens.Compose.Layers
private SelectionBox currentSelectionBox;
public void AddSelectionOverlay() => AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer));
public void AddSelectionOverlay()
{
if (overlayContainer.Count > 0)
AddInternal(currentSelectionBox = composer.CreateSelectionOverlay(overlayContainer));
}
public void RemoveSelectionOverlay()
{
+1 -1
View File
@@ -211,7 +211,7 @@ namespace osu.Game.Screens.Menu
rectangle,
colourInfo,
null,
Shared.VertexBatch.Add,
Shared.VertexBatch.AddAction,
//barSize by itself will make it smooth more in the X axis than in the Y axis, this reverts that.
Vector2.Divide(inflation, barSize.Yx));
}
+1 -7
View File
@@ -337,12 +337,10 @@ namespace osu.Game.Screens.Menu
}
}
private bool interactive => Action != null && Alpha > 0.2f;
public override bool HandleMouseInput => base.HandleMouseInput && Action != null && Alpha > 0.2f;
protected override bool OnMouseDown(InputState state, MouseDownEventArgs args)
{
if (!interactive) return false;
logoBounceContainer.ScaleTo(0.9f, 1000, Easing.Out);
return true;
}
@@ -355,8 +353,6 @@ namespace osu.Game.Screens.Menu
protected override bool OnClick(InputState state)
{
if (!interactive) return false;
if (Action?.Invoke() ?? true)
sampleClick.Play();
@@ -368,8 +364,6 @@ namespace osu.Game.Screens.Menu
protected override bool OnHover(InputState state)
{
if (!interactive) return false;
logoHoverContainer.ScaleTo(1.1f, 500, Easing.OutElastic);
return true;
}
+6 -1
View File
@@ -26,6 +26,7 @@ using osu.Game.Rulesets.Mods;
using osu.Game.Rulesets.Scoring;
using osu.Game.Rulesets.UI;
using osu.Game.Screens.Ranking;
using osu.Game.Skinning;
using osu.Game.Storyboards.Drawables;
namespace osu.Game.Screens.Play
@@ -163,7 +164,11 @@ namespace osu.Game.Screens.Play
RelativeSizeAxes = Axes.Both,
Alpha = 0,
},
RulesetContainer,
new LocalSkinOverrideContainer(working.Skin)
{
RelativeSizeAxes = Axes.Both,
Child = RulesetContainer
},
new SkipOverlay(firstObjectTime)
{
Clock = Clock, // skip button doesn't want to use the audio clock directly
+29 -4
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.Threading.Tasks;
using osu.Framework.Allocation;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
@@ -27,6 +28,8 @@ namespace osu.Game.Screens.Play
private bool showOverlays = true;
public override bool ShowOverlaysOnEnter => showOverlays;
private Task loadTask;
public PlayerLoader(Player player)
{
this.player = player;
@@ -55,7 +58,7 @@ namespace osu.Game.Screens.Play
Margin = new MarginPadding(25)
});
LoadComponentAsync(player);
loadTask = LoadComponentAsync(player);
}
protected override void OnResuming(Screen last)
@@ -65,7 +68,7 @@ namespace osu.Game.Screens.Play
contentIn();
//we will only be resumed if the player has requested a re-run (see ValidForResume setting above)
LoadComponentAsync(player = new Player
loadTask = LoadComponentAsync(player = new Player
{
RestartCount = player.RestartCount + 1,
RestartRequested = player.RestartRequested,
@@ -139,8 +142,7 @@ namespace osu.Game.Screens.Play
{
// as the pushDebounce below has a delay, we need to keep checking and cancel a future debounce
// if we become unready for push during the delay.
pushDebounce?.Cancel();
pushDebounce = null;
cancelLoad();
return;
}
@@ -155,6 +157,8 @@ namespace osu.Game.Screens.Play
{
if (!IsCurrentScreen) return;
loadTask = null;
if (!Push(player))
Exit();
else
@@ -172,14 +176,35 @@ namespace osu.Game.Screens.Play
}
}
private void cancelLoad()
{
pushDebounce?.Cancel();
pushDebounce = null;
}
protected override void OnSuspending(Screen next)
{
base.OnSuspending(next);
cancelLoad();
}
protected override bool OnExiting(Screen next)
{
Content.ScaleTo(0.7f, 150, Easing.InQuint);
this.FadeOut(150);
cancelLoad();
return base.OnExiting(next);
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
// if the player never got pushed, we should explicitly dispose it.
loadTask?.ContinueWith(_ => player.Dispose());
}
private class BeatmapMetadataDisplay : Container
{
private class MetadataLine : Container
@@ -46,6 +46,12 @@ namespace osu.Game.Screens.Play
UpdateBackgroundElements();
}
protected override void OnResuming(Screen last)
{
base.OnResuming(last);
UpdateBackgroundElements();
}
protected virtual void UpdateBackgroundElements()
{
if (!IsCurrentScreen) return;
@@ -328,7 +328,10 @@ namespace osu.Game.Screens.Select
public void FlushPendingFilterOperations()
{
if (FilterTask?.Completed == false)
{
applyActiveCriteria(false, false);
Update();
}
}
public void Filter(FilterCriteria newCriteria, bool debounce = true)
+2
View File
@@ -259,6 +259,8 @@ namespace osu.Game.Screens.Select
private void workingBeatmapChanged(WorkingBeatmap beatmap)
{
if (beatmap is DummyWorkingBeatmap) return;
if (IsCurrentScreen && !Carousel.SelectBeatmap(beatmap?.BeatmapInfo, false))
// If selecting new beatmap without bypassing filters failed, there's possibly a ruleset mismatch
if (beatmap?.BeatmapInfo?.Ruleset != null && beatmap.BeatmapInfo.Ruleset != Ruleset.Value)
+16 -8
View File
@@ -3,6 +3,8 @@
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using OpenTK.Graphics;
namespace osu.Game.Skinning
{
@@ -11,16 +13,22 @@ namespace osu.Game.Skinning
public DefaultSkin()
: base(SkinInfo.Default)
{
Configuration = new SkinConfiguration
{
ComboColours =
{
new Color4(17, 136, 170, 255),
new Color4(102, 136, 0, 255),
new Color4(204, 102, 0, 255),
new Color4(121, 9, 13, 255)
}
};
}
public override Drawable GetDrawableComponent(string componentName)
{
return null;
}
public override Drawable GetDrawableComponent(string componentName) => null;
public override SampleChannel GetSample(string sampleName)
{
return null;
}
public override Texture GetTexture(string componentName) => null;
public override SampleChannel GetSample(string sampleName) => null;
}
}
+28
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;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Skinning
{
/// <summary>
/// Provides access to skinnable elements.
/// </summary>
public interface ISkinSource
{
event Action SourceChanged;
Drawable GetDrawableComponent(string componentName);
Texture GetTexture(string componentName);
SampleChannel GetSample(string sampleName);
TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class;
TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct;
}
}
+20
View File
@@ -0,0 +1,20 @@
// 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.Audio;
using osu.Framework.IO.Stores;
using osu.Game.Beatmaps;
namespace osu.Game.Skinning
{
public class LegacyBeatmapSkin : LegacySkin
{
public LegacyBeatmapSkin(BeatmapInfo beatmap, IResourceStore<byte[]> storage, AudioManager audioManager)
: base(createSkinInfo(beatmap), new LegacySkinResourceStore<BeatmapSetFileInfo>(beatmap.BeatmapSet, storage), audioManager, beatmap.Path)
{
}
private static SkinInfo createSkinInfo(BeatmapInfo beatmap) =>
new SkinInfo { Name = beatmap.ToString(), Creator = beatmap.Metadata.Author.ToString() };
}
}
+54 -17
View File
@@ -10,21 +10,33 @@ using osu.Framework.Graphics;
using osu.Framework.Graphics.Sprites;
using osu.Framework.Graphics.Textures;
using osu.Framework.IO.Stores;
using osu.Game.Database;
using OpenTK;
namespace osu.Game.Skinning
{
public class LegacySkin : Skin
{
private readonly TextureStore textures;
protected TextureStore Textures;
private readonly SampleManager samples;
protected SampleManager Samples;
public LegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager)
: base(skin)
: this(skin, new LegacySkinResourceStore<SkinFileInfo>(skin, storage), audioManager, "skin.ini")
{
storage = new LegacySkinResourceStore(skin, storage);
samples = audioManager.GetSampleManager(storage);
textures = new TextureStore(new RawTextureLoaderStore(storage));
}
protected LegacySkin(SkinInfo skin, IResourceStore<byte[]> storage, AudioManager audioManager, string filename) : base(skin)
{
Stream stream = storage.GetStream(filename);
if (stream != null)
using (StreamReader reader = new StreamReader(stream))
Configuration = new LegacySkinDecoder().Decode(reader);
else
Configuration = new SkinConfiguration();
Samples = audioManager.GetSampleManager(storage);
Textures = new TextureStore(new RawTextureLoaderStore(storage));
}
public override Drawable GetDrawableComponent(string componentName)
@@ -45,37 +57,62 @@ namespace osu.Game.Skinning
break;
}
var texture = textures.Get(componentName);
float ratio = 0.72f; // brings sizing roughly in-line with stable
var texture = GetTexture($"{componentName}@2x");
if (texture == null)
{
ratio *= 2;
GetTexture(componentName);
}
if (texture == null) return null;
return new Sprite { Texture = texture };
return new Sprite
{
Texture = texture,
Scale = new Vector2(ratio),
};
}
public override SampleChannel GetSample(string sampleName) => samples.Get(sampleName);
public override Texture GetTexture(string componentName) => Textures.Get(componentName);
private class LegacySkinResourceStore : IResourceStore<byte[]>
public override SampleChannel GetSample(string sampleName) => Samples.Get(sampleName);
protected class LegacySkinResourceStore<T> : IResourceStore<byte[]>
where T : INamedFileInfo
{
private readonly SkinInfo skin;
private readonly IHasFiles<T> source;
private readonly IResourceStore<byte[]> underlyingStore;
private string getPathForFile(string filename)
{
bool hasExtension = filename.Contains('.');
string lastPiece = filename.Split('/').Last();
var file = skin.Files.FirstOrDefault(f =>
string.Equals(Path.GetFileNameWithoutExtension(f.Filename), lastPiece, StringComparison.InvariantCultureIgnoreCase));
var file = source.Files.FirstOrDefault(f =>
string.Equals(hasExtension ? f.Filename : Path.GetFileNameWithoutExtension(f.Filename), lastPiece, StringComparison.InvariantCultureIgnoreCase));
return file?.FileInfo.StoragePath;
}
public LegacySkinResourceStore(SkinInfo skin, IResourceStore<byte[]> underlyingStore)
public LegacySkinResourceStore(IHasFiles<T> source, IResourceStore<byte[]> underlyingStore)
{
this.skin = skin;
this.source = source;
this.underlyingStore = underlyingStore;
}
public Stream GetStream(string name) => underlyingStore.GetStream(getPathForFile(name));
public Stream GetStream(string name)
{
string path = getPathForFile(name);
return path == null ? null : underlyingStore.GetStream(path);
}
byte[] IResourceStore<byte[]>.Get(string name) => underlyingStore.Get(getPathForFile(name));
byte[] IResourceStore<byte[]>.Get(string name)
{
string path = getPathForFile(name);
return path == null ? null : underlyingStore.Get(path);
}
}
}
}
+38
View File
@@ -0,0 +1,38 @@
// 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.Beatmaps.Formats;
namespace osu.Game.Skinning
{
public class LegacySkinDecoder : LegacyDecoder<SkinConfiguration>
{
public LegacySkinDecoder()
: base(1)
{
}
protected override void ParseLine(SkinConfiguration output, Section section, string line)
{
switch (section)
{
case Section.General:
var pair = SplitKeyVal(line);
switch (pair.Key)
{
case @"Name":
output.SkinInfo.Name = pair.Value;
break;
case @"Author":
output.SkinInfo.Creator = pair.Value;
break;
}
break;
}
base.ParseLine(output, section, line);
}
}
}
@@ -0,0 +1,74 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Containers;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Skinning
{
public class LocalSkinOverrideContainer : Container, ISkinSource
{
public event Action SourceChanged;
public Drawable GetDrawableComponent(string componentName) => source.GetDrawableComponent(componentName) ?? fallbackSource?.GetDrawableComponent(componentName);
public Texture GetTexture(string componentName) => source.GetTexture(componentName) ?? fallbackSource.GetTexture(componentName);
public SampleChannel GetSample(string sampleName) => source.GetSample(sampleName) ?? fallbackSource?.GetSample(sampleName);
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct
{
TValue? val = null;
var conf = (source as Skin)?.Configuration as TConfiguration;
if (conf != null)
val = query?.Invoke(conf);
return val ?? fallbackSource?.GetValue(query);
}
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class
{
TValue val = null;
var conf = (source as Skin)?.Configuration as TConfiguration;
if (conf != null)
val = query?.Invoke(conf);
return val ?? fallbackSource?.GetValue(query);
}
private readonly ISkinSource source;
private ISkinSource fallbackSource;
public LocalSkinOverrideContainer(ISkinSource source)
{
this.source = source;
}
private void onSourceChanged() => SourceChanged?.Invoke();
protected override IReadOnlyDependencyContainer CreateLocalDependencies(IReadOnlyDependencyContainer parent)
{
var dependencies = new DependencyContainer(base.CreateLocalDependencies(parent));
fallbackSource = dependencies.Get<ISkinSource>();
if (fallbackSource != null)
fallbackSource.SourceChanged += onSourceChanged;
dependencies.CacheAs<ISkinSource>(this);
return dependencies;
}
protected override void Dispose(bool isDisposing)
{
base.Dispose(isDisposing);
if (fallbackSource != null)
fallbackSource.SourceChanged -= onSourceChanged;
}
}
}
+39 -1
View File
@@ -1,22 +1,60 @@
// 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 osu.Framework.Audio.Sample;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
namespace osu.Game.Skinning
{
public abstract class Skin
public abstract class Skin : IDisposable, ISkinSource
{
public readonly SkinInfo SkinInfo;
public virtual SkinConfiguration Configuration { get; protected set; }
public event Action SourceChanged;
public abstract Drawable GetDrawableComponent(string componentName);
public abstract SampleChannel GetSample(string sampleName);
public abstract Texture GetTexture(string componentName);
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class
=> Configuration is TConfiguration conf ? query?.Invoke(conf) : null;
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct
=> Configuration is TConfiguration conf ? query?.Invoke(conf) : null;
protected Skin(SkinInfo skin)
{
SkinInfo = skin;
}
#region Disposal
~Skin()
{
Dispose(false);
}
public void Dispose()
{
Dispose(true);
GC.SuppressFinalize(this);
}
private bool isDisposed;
protected virtual void Dispose(bool isDisposing)
{
if (isDisposed)
return;
isDisposed = true;
}
#endregion
}
}
+18
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 System.Collections.Generic;
using osu.Game.Beatmaps.Formats;
using OpenTK.Graphics;
namespace osu.Game.Skinning
{
public class SkinConfiguration : IHasComboColours, IHasCustomColours
{
public readonly SkinInfo SkinInfo = new SkinInfo();
public List<Color4> ComboColours { get; set; } = new List<Color4>();
public Dictionary<string, Color4> CustomColours { get; set; } = new Dictionary<string, Color4>();
}
}
+2
View File
@@ -24,5 +24,7 @@ namespace osu.Game.Skinning
public static SkinInfo Default { get; } = new SkinInfo { Name = "osu!lazer", Creator = "team osu!" };
public bool Equals(SkinInfo other) => other != null && ID == other.ID;
public override string ToString() => $"\"{Name}\" by {Creator}";
}
}
+53 -1
View File
@@ -7,14 +7,17 @@ using System.Linq;
using System.Linq.Expressions;
using Microsoft.EntityFrameworkCore;
using osu.Framework.Audio;
using osu.Framework.Audio.Sample;
using osu.Framework.Configuration;
using osu.Framework.Graphics;
using osu.Framework.Graphics.Textures;
using osu.Framework.Platform;
using osu.Game.Database;
using osu.Game.IO.Archives;
namespace osu.Game.Skinning
{
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>
public class SkinManager : ArchiveModelManager<SkinInfo, SkinFileInfo>, ISkinSource
{
private readonly AudioManager audio;
@@ -39,6 +42,31 @@ namespace osu.Game.Skinning
Name = archive.Name
};
protected override void Populate(SkinInfo model, ArchiveReader archive)
{
base.Populate(model, archive);
populate(model);
}
/// <summary>
/// Populate a <see cref="SkinInfo"/> from its <see cref="SkinConfiguration"/> (if possible).
/// </summary>
/// <param name="model"></param>
private void populate(SkinInfo model)
{
Skin reference = GetSkin(model);
if (!string.IsNullOrEmpty(reference.Configuration.SkinInfo.Name))
{
model.Name = reference.Configuration.SkinInfo.Name;
model.Creator = reference.Configuration.SkinInfo.Creator;
}
else
{
model.Name = model.Name.Replace(".osk", "");
model.Creator = "Unknown";
}
}
/// <summary>
/// Retrieve a <see cref="Skin"/> instance for the provided <see cref="SkinInfo"/>
/// </summary>
@@ -64,7 +92,19 @@ namespace osu.Game.Skinning
{
if (skin.SkinInfo != CurrentSkinInfo.Value)
throw new InvalidOperationException($"Setting {nameof(CurrentSkin)}'s value directly is not supported. Use {nameof(CurrentSkinInfo)} instead.");
SourceChanged?.Invoke();
};
// migrate older imports which didn't have access to skin.ini
using (ContextFactory.GetForWrite())
{
foreach (var skinInfo in ModelStore.ConsumableItems.Where(s => s.Name.EndsWith(".osk")))
{
populate(skinInfo);
Update(skinInfo);
}
}
}
/// <summary>
@@ -73,5 +113,17 @@ namespace osu.Game.Skinning
/// <param name="query">The query.</param>
/// <returns>The first result for the provided query, or null if no results were found.</returns>
public SkinInfo Query(Expression<Func<SkinInfo, bool>> query) => ModelStore.ConsumableItems.AsNoTracking().FirstOrDefault(query);
public event Action SourceChanged;
public Drawable GetDrawableComponent(string componentName) => CurrentSkin.Value.GetDrawableComponent(componentName);
public Texture GetTexture(string componentName) => CurrentSkin.Value.GetTexture(componentName);
public SampleChannel GetSample(string sampleName) => CurrentSkin.Value.GetSample(sampleName);
public TValue GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue> query) where TConfiguration : SkinConfiguration where TValue : class => CurrentSkin.Value.GetValue(query);
public TValue? GetValue<TConfiguration, TValue>(Func<TConfiguration, TValue?> query) where TConfiguration : SkinConfiguration where TValue : struct => CurrentSkin.Value.GetValue(query);
}
}
+13 -10
View File
@@ -1,8 +1,8 @@
// 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 osu.Framework.Allocation;
using osu.Framework.Configuration;
using osu.Framework.Graphics.Containers;
namespace osu.Game.Skinning
@@ -12,33 +12,36 @@ namespace osu.Game.Skinning
/// </summary>
public abstract class SkinReloadableDrawable : CompositeDrawable
{
private Bindable<Skin> skin;
private readonly Func<ISkinSource, bool> allowFallback;
private ISkinSource skin;
/// <summary>
/// Whether fallback to default skin should be allowed if the custom skin is missing this resource.
/// </summary>
private readonly bool allowDefaultFallback;
private bool allowDefaultFallback => allowFallback == null || allowFallback.Invoke(skin);
/// <summary>
/// Create a new <see cref="SkinReloadableDrawable"/>
/// </summary>
/// <param name="fallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected SkinReloadableDrawable(bool fallback = true)
protected SkinReloadableDrawable(Func<ISkinSource, bool> allowFallback = null)
{
allowDefaultFallback = fallback;
this.allowFallback = allowFallback;
}
[BackgroundDependencyLoader]
private void load(SkinManager skinManager)
private void load(ISkinSource source)
{
skin = skinManager.CurrentSkin.GetBoundCopy();
skin.ValueChanged += skin => SkinChanged(skin, allowDefaultFallback || skin.SkinInfo == SkinInfo.Default);
skin = source;
skin.SourceChanged += onChange;
}
private void onChange() => SkinChanged(skin, allowDefaultFallback);
protected override void LoadAsyncComplete()
{
base.LoadAsyncComplete();
skin.TriggerChange();
onChange();
}
/// <summary>
@@ -46,7 +49,7 @@ namespace osu.Game.Skinning
/// </summary>
/// <param name="skin">The new skin.</param>
/// <param name="allowFallback">Whether fallback to default skin should be allowed if the custom skin is missing this resource.</param>
protected virtual void SkinChanged(Skin skin, bool allowFallback)
protected virtual void SkinChanged(ISkinSource skin, bool allowFallback)
{
}
}

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